Create a new API for handling merged annotations

Add new `MergedAnnotations` and `MergedAnnotation` interfaces that
attempt to provide a uniform way for dealing with merged annotations.

Specifically, the new API provides alternatives for the static methods
in `AnnotationUtils` and `AnnotatedElementUtils` and supports Spring's
comprehensive annotation attribute `@AliasFor` features. The interfaces
also open the possibility of the same API being exposed from the
`AnnotationMetadata` interface.

Additional utility classes for collecting, filtering and selecting
annotations have also been added.

Typical usage for the new API would be something like:

	MergedAnnotations.from(Example.class)
		.stream(MyAnnotation.class)
		.map(a -> a.getString("value"))
		.forEach(System.out::println);

Closes gh-21697
This commit is contained in:
Phillip Webb
2019-01-31 11:03:57 -08:00
committed by Juergen Hoeller
parent fdacda8b01
commit 4972d85ae0
35 changed files with 13436 additions and 0 deletions

View File

@@ -0,0 +1,226 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Predicate;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Abstract base class for {@link MergedAnnotation} implementations.
*
* @author Phillip Webb
* @since 5.2
* @param <A> the annotation type
*/
abstract class AbstractMergedAnnotation<A extends Annotation>
implements MergedAnnotation<A> {
@Nullable
private volatile A synthesizedAnnotation;
@Override
public boolean isDirectlyPresent() {
return isPresent() && getDepth() == 0;
}
@Override
public boolean isMetaPresent() {
return isPresent() && getDepth() > 0;
}
@Override
public boolean hasNonDefaultValue(String attributeName) {
return !hasDefaultValue(attributeName);
}
public byte getByte(String attributeName) {
return getRequiredAttributeValue(attributeName, Byte.class);
}
public byte[] getByteArray(String attributeName) {
return getRequiredAttributeValue(attributeName, byte[].class);
}
public boolean getBoolean(String attributeName) {
return getRequiredAttributeValue(attributeName, Boolean.class);
}
public boolean[] getBooleanArray(String attributeName) {
return getRequiredAttributeValue(attributeName, boolean[].class);
}
public char getChar(String attributeName) {
return getRequiredAttributeValue(attributeName, Character.class);
}
public char[] getCharArray(String attributeName) {
return getRequiredAttributeValue(attributeName, char[].class);
}
public short getShort(String attributeName) {
return getRequiredAttributeValue(attributeName, Short.class);
}
public short[] getShortArray(String attributeName) {
return getRequiredAttributeValue(attributeName, short[].class);
}
public int getInt(String attributeName) {
return getRequiredAttributeValue(attributeName, Integer.class);
}
public int[] getIntArray(String attributeName) {
return getRequiredAttributeValue(attributeName, int[].class);
}
public long getLong(String attributeName) {
return getRequiredAttributeValue(attributeName, Long.class);
}
public long[] getLongArray(String attributeName) {
return getRequiredAttributeValue(attributeName, long[].class);
}
public double getDouble(String attributeName) {
return getRequiredAttributeValue(attributeName, Double.class);
}
public double[] getDoubleArray(String attributeName) {
return getRequiredAttributeValue(attributeName, double[].class);
}
public float getFloat(String attributeName) {
return getRequiredAttributeValue(attributeName, Float.class);
}
public float[] getFloatArray(String attributeName) {
return getRequiredAttributeValue(attributeName, float[].class);
}
public String getString(String attributeName) {
return getRequiredAttributeValue(attributeName, String.class);
}
public String[] getStringArray(String attributeName) {
return getRequiredAttributeValue(attributeName, String[].class);
}
public Class<?> getClass(String attributeName) {
return getRequiredAttributeValue(attributeName, Class.class);
}
public Class<?>[] getClassArray(String attributeName) {
return getRequiredAttributeValue(attributeName, Class[].class);
}
public <E extends Enum<E>> E getEnum(String attributeName, Class<E> type) {
Assert.notNull(type, "Type must not be null");
return getRequiredAttributeValue(attributeName, type);
}
@SuppressWarnings("unchecked")
public <E extends Enum<E>> E[] getEnumArray(String attributeName, Class<E> type) {
Assert.notNull(type, "Type must not be null");
Class<?> arrayType = Array.newInstance(type, 0).getClass();
return (E[]) getRequiredAttributeValue(attributeName, arrayType);
}
@Override
public Optional<Object> getValue(String attributeName) {
return getValue(attributeName, Object.class);
}
@Override
public <T> Optional<T> getValue(String attributeName, Class<T> type) {
return Optional.ofNullable(getAttributeValue(attributeName, type));
}
@Override
public Optional<Object> getDefaultValue(String attributeName) {
return getDefaultValue(attributeName, Object.class);
}
@Override
public MergedAnnotation<A> filterDefaultValues() {
return filterAttributes(this::hasNonDefaultValue);
}
@Override
public Map<String, Object> asMap(MapValues... options) {
return asMap(null, options);
}
@Override
public Optional<A> synthesize(
@Nullable Predicate<? super MergedAnnotation<A>> condition)
throws NoSuchElementException {
if (condition == null || condition.test(this)) {
return Optional.of(synthesize());
}
return Optional.empty();
}
@Override
public A synthesize() {
if (!isPresent()) {
throw new NoSuchElementException("Unable to synthesize missing annotation");
}
A synthesized = this.synthesizedAnnotation;
if (synthesized == null) {
synthesized = createSynthesized();
this.synthesizedAnnotation = synthesized;
}
return synthesized;
}
private <T> T getRequiredAttributeValue(String attributeName, Class<T> type) {
T value = getAttributeValue(attributeName, type);
if (value == null) {
throw new NoSuchElementException("No attribute named '" + attributeName +
"' present in merged annotation " + getType());
}
return value;
}
/**
* Get the underlying attribute value.
* @param attributeName the attribute name
* @param type the type to return (see {@link MergedAnnotation} class
* documentation for details).
* @return the attribute value or {@code null} if the value is not found and
* is not required
* @throws IllegalArgumentException if the source type is not compatible
* @throws NoSuchElementException if the value is required but not found
*/
@Nullable
protected abstract <T> T getAttributeValue(String attributeName, Class<T> type);
/**
* Factory method used to create the synthesized annotation.
*/
protected abstract A createSynthesized();
}

View File

@@ -0,0 +1,179 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Callback interface that can be used to filter specific annotation types.
*
* @author Phillip Webb
* @since 5.2
*/
@FunctionalInterface
public interface AnnotationFilter {
/**
* {@link AnnotationFilter} that matches annotations is in the
* {@code java.lang.*} or in the
* {@code org.springframework.lang.*} package.
*/
static final AnnotationFilter PLAIN = packages("java.lang",
"org.springframework.lang");
/**
* {@link AnnotationFilter} that matches annotations in the
* {@code java.lang.*} package.
*/
static final AnnotationFilter JAVA = packages("java.lang");
/**
* {@link AnnotationFilter} that never matches and can be used when no
* filtering is needed.
*/
static final AnnotationFilter NONE = new AnnotationFilter() {
@Override
public boolean matches(@Nullable String typeName) {
return false;
}
@Override
public String toString() {
return "No annotation filtering";
}
};
/**
* Test if the given annotation matches the filter.
* @param annotation the annotation to test
* @return {@code true} if the annotation matches
*/
default boolean matches(@Nullable Annotation annotation) {
return matches(annotation != null ? annotation.annotationType() : null);
}
/**
* Test if the given type matches the filter.
* @param type the annotation type to test
* @return {@code true} if the annotation matches
*/
default boolean matches(@Nullable Class<?> type) {
return matches(type != null ? type.getName() : null);
}
/**
* Test if the given type name matches the filter.
* @param typeName the annotation type to test
* @return {@code true} if the annotation matches
*/
boolean matches(@Nullable String typeName);
/**
* Return a new {@link AnnotationFilter} that matches annotations in the
* specified packages.
* @param packages the annotation packages that should match
* @return a new {@link AnnotationFilter} instance
*/
static AnnotationFilter packages(String... packages) {
return new PackagesAnnotationFilter(packages);
}
/**
* Return an {@link AnnotationFilter} that is the most appropriate for, and
* will always match the given annotation type. Whenever possible,
* {@link AnnotationFilter#PLAIN} will be returned.
* @param annotationType the annotation type to check
* @return the most appropriate annotation filter
*/
static AnnotationFilter mostAppropriateFor(@Nullable Class<?> annotationType) {
return PLAIN.matches(annotationType) ? NONE : PLAIN;
}
/**
* Return an {@link AnnotationFilter} that is the most appropriate for, and
* will always match all the given annotation types. Whenever possible,
* {@link AnnotationFilter#PLAIN} will be returned.
* @param annotationTypes the annotation types to check
* @return the most appropriate annotation filter
*/
static AnnotationFilter mostAppropriateFor(Class<?>... annotationTypes) {
Assert.notNull(annotationTypes, "AnnotationTypes must not be null");
return mostAppropriateFor(Arrays.asList(annotationTypes));
}
/**
* Return an {@link AnnotationFilter} that is the most appropriate for, and
* will always match all the given annotation type. Whenever possible,
* {@link AnnotationFilter#PLAIN} will be returned.
* @param annotationType the annotation type to check
* @return the most appropriate annotation filter
*/
static AnnotationFilter mostAppropriateFor(@Nullable String annotationType) {
return PLAIN.matches(annotationType) ? NONE : PLAIN;
}
/**
* Return an {@link AnnotationFilter} that is the most appropriate for, and
* will always match all the given annotation types. Whenever possible,
* {@link AnnotationFilter#PLAIN} will be returned.
* @param annotationTypes the annotation types to check
* @return the most appropriate annotation filter
*/
static AnnotationFilter mostAppropriateFor(String... annotationTypes) {
Assert.notNull(annotationTypes, "AnnotationTypes must not be null");
return mostAppropriateFor(Arrays.asList(annotationTypes));
}
/**
* Return an {@link AnnotationFilter} that is the most appropriate for, and
* will always match all the given annotation types. Whenever possible,
* {@link AnnotationFilter#PLAIN} will be returned.
* @param annotationTypes the annotation types to check (may be class names
* or class types)
* @return the most appropriate annotation filter
*/
@SuppressWarnings("unchecked")
static AnnotationFilter mostAppropriateFor(Collection<?> annotationTypes) {
Assert.notNull(annotationTypes, "AnnotationTypes must not be null");
for (Object annotationType : annotationTypes) {
if (annotationType == null) {
continue;
}
Assert.isTrue(
annotationType instanceof Class || annotationType instanceof String,
"AnnotationType must be a Class or String");
if (annotationType instanceof Class
&& PLAIN.matches((Class<Annotation>) annotationType)) {
return NONE;
}
if (annotationType instanceof String
&& PLAIN.matches((String) annotationType)) {
return NONE;
}
}
return PLAIN;
}
}

View File

@@ -0,0 +1,619 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.MirrorSet;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* Provides mapping information for a single annotation (or meta-annotation) in
* the context of a root annotation type.
*
* @author Phillip Webb
* @since 5.2
* @see AnnotationTypeMappings
*/
class AnnotationTypeMapping {
@Nullable
private final AnnotationTypeMapping parent;
private final AnnotationTypeMapping root;
private final int depth;
private final Class<? extends Annotation> annotationType;
@Nullable
private final Annotation annotation;
private final AttributeMethods attributes;
private final MirrorSets mirrorSets;
private final int[] aliasMappings;
private final int[] conventionMappings;
private final Map<Method, List<Method>> aliasedBy;
private final Set<Method> claimedAliases = new HashSet<>();
AnnotationTypeMapping(Class<? extends Annotation> annotationType) {
this(null, annotationType, null);
}
AnnotationTypeMapping(AnnotationTypeMapping parent, Annotation annotation) {
this(parent, annotation.annotationType(), annotation);
}
AnnotationTypeMapping(@Nullable AnnotationTypeMapping parent,
Class<? extends Annotation> annotationType, @Nullable Annotation annotation) {
this.parent = parent;
this.root = parent != null ? parent.getRoot() : this;
this.depth = parent == null ? 0 : parent.getDepth() + 1;
this.annotationType = annotationType;
this.annotation = annotation;
this.attributes = AttributeMethods.forAnnotationType(annotationType);
this.mirrorSets = new MirrorSets();
this.aliasMappings = filledIntArray(this.attributes.size(), -1);
this.conventionMappings = filledIntArray(this.attributes.size(), -1);
this.aliasedBy = resolveAliasedForTargets();
processAliases();
addConventionMappings();
}
private Map<Method, List<Method>> resolveAliasedForTargets() {
Map<Method, List<Method>> aliasedBy = new HashMap<>();
for (int i = 0; i < this.attributes.size(); i++) {
Method attribute = this.attributes.get(i);
AliasFor aliasFor = AnnotationsScanner.getDeclaredAnnotation(attribute,
AliasFor.class);
if (aliasFor != null) {
Method target = resolveAliasTarget(attribute, aliasFor);
aliasedBy.computeIfAbsent(target, key -> new ArrayList<>()).add(
attribute);
}
}
return Collections.unmodifiableMap(aliasedBy);
}
private Method resolveAliasTarget(Method attribute, AliasFor aliasFor) {
return resolveAliasTarget(attribute, aliasFor, true);
}
private Method resolveAliasTarget(Method attribute, AliasFor aliasFor, boolean checkAliasPair) {
if (StringUtils.hasText(aliasFor.value()) &&
StringUtils.hasText(aliasFor.attribute())) {
throw new AnnotationConfigurationException(String.format(
"In @AliasFor declared on %s, attribute 'attribute' and its alias "
+ "'value' are present with values of '%s' and '%s', but "
+ "only one is permitted.",
AttributeMethods.describe(attribute), aliasFor.attribute(),
aliasFor.value()));
}
Class<? extends Annotation> targetAnnotation = aliasFor.annotation();
if (targetAnnotation == Annotation.class) {
targetAnnotation = this.annotationType;
}
String targetAttributeName = aliasFor.attribute();
if (!StringUtils.hasLength(targetAttributeName)) {
targetAttributeName = aliasFor.value();
}
if (!StringUtils.hasLength(targetAttributeName)) {
targetAttributeName = attribute.getName();
}
Method target = AttributeMethods.forAnnotationType(targetAnnotation)
.get(targetAttributeName);
if (target == null) {
if (targetAnnotation == this.annotationType) {
throw new AnnotationConfigurationException(String.format(
"@AliasFor declaration on %s declares an "
+ "alias for '%s' which is not present.",
AttributeMethods.describe(attribute), targetAttributeName));
}
throw new AnnotationConfigurationException(String.format(
"%s is declared as an @AliasFor nonexistent %s.",
StringUtils.capitalize(AttributeMethods.describe(attribute)),
AttributeMethods.describe(targetAnnotation, targetAttributeName)));
}
if (target == attribute) {
throw new AnnotationConfigurationException(String.format(
"@AliasFor declaration on %s points to itself. "
+ "Specify 'annotation' to point to a same-named "
+ "attribute on a meta-annotation.",
AttributeMethods.describe(attribute)));
}
if (!isCompatibleReturnType(attribute.getReturnType(), target.getReturnType())) {
throw new AnnotationConfigurationException(String.format(
"Misconfigured aliases: %s and %s must declare the same return type.",
AttributeMethods.describe(attribute),
AttributeMethods.describe(target)));
}
if (isAliasPair(target) && checkAliasPair) {
AliasFor targetAliasFor = target.getAnnotation(AliasFor.class);
if (targetAliasFor == null) {
throw new AnnotationConfigurationException(
String.format("%s must be declared as an @AliasFor '%s'.",
StringUtils.capitalize(AttributeMethods.describe(target)),
attribute.getName()));
}
Method mirror = resolveAliasTarget(target, targetAliasFor, false);
if (mirror != attribute) {
throw new AnnotationConfigurationException(String.format(
"%s must be declared as an @AliasFor '%s', not '%s'.",
StringUtils.capitalize(AttributeMethods.describe(target)),
attribute.getName(), mirror.getName()));
}
}
return target;
}
private boolean isAliasPair(Method target) {
return target.getDeclaringClass().equals(this.annotationType);
}
private boolean isCompatibleReturnType(Class<?> attributeType, Class<?> targetType) {
return Objects.equals(attributeType, targetType) ||
Objects.equals(attributeType, targetType.getComponentType());
}
private void processAliases() {
List<Method> aliases = new ArrayList<>();
for (int i = 0; i < this.attributes.size(); i++) {
aliases.clear();
aliases.add(this.attributes.get(i));
collectAliases(aliases);
if (aliases.size() > 1) {
processAliases(aliases);
}
}
}
private void collectAliases(List<Method> aliases) {
AnnotationTypeMapping mapping = this;
while (mapping != null) {
int size = aliases.size();
for (int j = 0; j < size; j++) {
List<Method> additional = mapping.aliasedBy.get(aliases.get(j));
if (additional != null) {
aliases.addAll(additional);
}
}
mapping = mapping.parent;
}
}
private void processAliases(List<Method> aliases) {
int rootAttributeIndex = getFirstRootAttributeIndex(aliases);
AnnotationTypeMapping mapping = this;
while (mapping != null) {
if (rootAttributeIndex != -1 && mapping != this.root) {
for (int i = 0; i < mapping.attributes.size(); i++) {
if (aliases.contains(mapping.attributes.get(i))) {
mapping.aliasMappings[i] = rootAttributeIndex;
}
}
}
mapping.mirrorSets.updateFrom(aliases);
mapping.claimedAliases.addAll(aliases);
mapping = mapping.parent;
}
}
private int getFirstRootAttributeIndex(Collection<Method> aliases) {
AttributeMethods rootAttributes = this.root.getAttributes();
for (int i = 0; i < rootAttributes.size(); i++) {
if (aliases.contains(rootAttributes.get(i))) {
return i;
}
}
return -1;
}
private void addConventionMappings() {
if (this.depth == 0) {
return;
}
AttributeMethods rootAttributes = this.root.getAttributes();
int[] mappings = this.conventionMappings;
for (int i = 0; i < mappings.length; i++) {
String name = this.attributes.get(i).getName();
MirrorSet mirrors = getMirrorSets().getAssigned(i);
int mapped = rootAttributes.indexOf(name);
if (!MergedAnnotation.VALUE.equals(name) && mapped != -1) {
mappings[i] = mapped;
if (mirrors != null) {
for (int j = 0; j < mirrors.size(); j++) {
mappings[mirrors.getAttributeIndex(j)] = mapped;
}
}
}
}
}
/**
* Method called after all mappings have been set. At this point no further
* lookups from child mappings will occur.
*/
void afterAllMappingsSet() {
validateAllAliasesClaimed();
for (int i = 0; i < this.mirrorSets.size(); i++) {
validateMirrorSet(this.mirrorSets.get(i));
}
this.claimedAliases.clear();
}
private void validateAllAliasesClaimed() {
for (int i = 0; i < this.attributes.size(); i++) {
Method attribute = this.attributes.get(i);
AliasFor aliasFor = AnnotationsScanner.getDeclaredAnnotation(attribute, AliasFor.class);
if (aliasFor != null && !this.claimedAliases.contains(attribute)) {
Method target = resolveAliasTarget(attribute, aliasFor);
throw new AnnotationConfigurationException(String.format(
"@AliasFor declaration on %s declares an alias for %s which is not meta-present.",
AttributeMethods.describe(attribute),
AttributeMethods.describe(target)));
}
}
}
private void validateMirrorSet(MirrorSet mirrorSet) {
Method firstAttribute = mirrorSet.get(0);
Object firstDefaultValue = firstAttribute.getDefaultValue();
for (int i = 1; i <= mirrorSet.size() - 1; i++) {
Method mirrorAttribute = mirrorSet.get(i);
Object mirrorDefaultValue = mirrorAttribute.getDefaultValue();
if (firstDefaultValue == null || mirrorDefaultValue == null) {
throw new AnnotationConfigurationException(String.format(
"Misconfigured aliases: %s and %s must declare default values.",
AttributeMethods.describe(firstAttribute),
AttributeMethods.describe(mirrorAttribute)));
}
if (!ObjectUtils.nullSafeEquals(firstDefaultValue, mirrorDefaultValue)) {
throw new AnnotationConfigurationException(String.format(
"Misconfigured aliases: %s and %s must declare the same default value.",
AttributeMethods.describe(firstAttribute),
AttributeMethods.describe(mirrorAttribute)));
}
}
}
/**
* Return the root mapping.
* @return the root mapping
*/
AnnotationTypeMapping getRoot() {
return this.root;
}
/**
* Return the parent mapping or {@code null}.
* @return the parent mapping
*/
@Nullable
AnnotationTypeMapping getParent() {
return this.parent;
}
/**
* Return the depth of this mapping.
* @return the depth of the mapping
*/
int getDepth() {
return this.depth;
}
/**
* Return the type of the mapped annotation.
* @return the annotation type
*/
Class<? extends Annotation> getAnnotationType() {
return this.annotationType;
}
/**
* Return the source annotation for this mapping. This will be the
* meta-annotation, or {@code null} if this is the root mapping.
* @return the source annotation of the mapping
*/
@Nullable
Annotation getAnnotation() {
return this.annotation;
}
/**
* Return the annotation attributes for the mapping annotation type.
* @return the attribute methods
*/
AttributeMethods getAttributes() {
return this.attributes;
}
/**
* Return the related index of an alias mapped attribute, or {@code -1} if
* there is no mapping. The resulting value is the index of the attribute on
* the root annotation that can be invoked in order to obtain the actual
* value.
* @param attributeIndex the attribute index of the source attribute
* @return the mapped attribute index or {@code -1}
*/
int getAliasMapping(int attributeIndex) {
return this.aliasMappings[attributeIndex];
}
/**
* Return the related index of a convention mapped attribute, or {@code -1}
* if there is no mapping. The resulting value is the index of the attribute
* on the root annotation that can be invoked in order to obtain the actual
* value.
* @param attributeIndex the attribute index of the source attribute
* @return the mapped attribute index or {@code -1}
*/
int getConventionMapping(int attributeIndex) {
return this.conventionMappings[attributeIndex];
}
/**
* Return if the specified value is equivalent to the default value of the
* attribute at the given index.
* @param attributeIndex the attribute index of the source attribute
* @param value the value to check
* @param valueExtractor the value extractor used to extract value from any
* nested annotations
* @return {@code true} if the value is equivalent to the default value
*/
boolean isEquivalentToDefaultValue(int attributeIndex, Object value,
BiFunction<Method, Object, Object> valueExtractor) {
Method attribute = this.attributes.get(attributeIndex);
return isEquivalentToDefaultValue(attribute, value, valueExtractor);
}
/**
* Return the mirror sets for this type mapping.
* @return the mirrorSets the attribute mirror sets.
*/
MirrorSets getMirrorSets() {
return this.mirrorSets;
}
private static int[] filledIntArray(int size, int value) {
int[] array = new int[size];
Arrays.fill(array, value);
return array;
}
private static boolean isEquivalentToDefaultValue(Method attribute, Object value,
BiFunction<Method, Object, Object> valueExtractor) {
return areEquivalent(attribute.getDefaultValue(), value, valueExtractor);
}
private static boolean areEquivalent(@Nullable Object value,
@Nullable Object extractedValue,
BiFunction<Method, Object, Object> valueExtractor) {
if (ObjectUtils.nullSafeEquals(value, extractedValue)) {
return true;
}
if (value instanceof Class && extractedValue instanceof String) {
return areEquivalent((Class<?>) value, (String) extractedValue);
}
if (value instanceof Class[] && extractedValue instanceof String[]) {
return areEquivalent((Class[]) value, (String[]) extractedValue);
}
if (value instanceof Annotation) {
return areEquivalent((Annotation) value, extractedValue, valueExtractor);
}
return false;
}
private static boolean areEquivalent(Class<?>[] value, String[] extractedValue) {
if (value.length != extractedValue.length) {
return false;
}
for (int i = 0; i < value.length; i++) {
if (!areEquivalent(value[i], extractedValue[i])) {
return false;
}
}
return true;
}
private static boolean areEquivalent(Class<?> value, String extractedValue) {
return value.getName().equals(extractedValue);
}
private static boolean areEquivalent(Annotation value, @Nullable Object extractedValue,
BiFunction<Method, Object, Object> valueExtractor) {
AttributeMethods attributes = AttributeMethods.forAnnotationType(
value.annotationType());
for (int i = 0; i < attributes.size(); i++) {
Method attribute = attributes.get(i);
if (!areEquivalent(ReflectionUtils.invokeMethod(attribute, value),
valueExtractor.apply(attribute, extractedValue), valueExtractor)) {
return false;
}
}
return true;
}
/**
* A collection of {@link MirrorSet} instances that provides details of all
* defined mirrors.
*/
class MirrorSets {
private MirrorSet[] mirrorSets;
private final MirrorSet[] assigned;
MirrorSets() {
this.assigned = new MirrorSet[attributes.size()];
this.mirrorSets = new MirrorSet[0];
}
void updateFrom(Collection<Method> aliases) {
MirrorSet mirrorSet = null;
int size = 0;
int last = -1;
for (int i = 0; i < attributes.size(); i++) {
Method attribute = attributes.get(i);
if (aliases.contains(attribute)) {
size++;
if (size > 1) {
if (mirrorSet == null) {
mirrorSet = new MirrorSet();
this.assigned[last] = mirrorSet;
}
this.assigned[i] = mirrorSet;
}
last = i;
}
}
if (mirrorSet != null) {
mirrorSet.update();
LinkedHashSet<MirrorSet> unique = new LinkedHashSet<>(
Arrays.asList(this.assigned));
unique.remove(null);
this.mirrorSets = unique.toArray(new MirrorSet[0]);
}
}
int size() {
return this.mirrorSets.length;
}
MirrorSet get(int index) {
return this.mirrorSets[index];
}
@Nullable
MirrorSet getAssigned(int attributeIndex) {
return this.assigned[attributeIndex];
}
int[] resolve(@Nullable Object source, @Nullable Object annotation,
BiFunction<Method, Object, Object> valueExtractor) {
int[] result = new int[attributes.size()];
for (int i = 0; i < result.length; i++) {
result[i] = i;
}
for (int i = 0; i < size(); i++) {
MirrorSet mirrorSet = get(i);
int resolved = mirrorSet.resolve(source, annotation, valueExtractor);
for (int j = 0; j < mirrorSet.size; j++) {
result[mirrorSet.indexes[j]] = resolved;
}
}
return result;
}
/**
* A single set of mirror attributes.
*/
class MirrorSet {
private int size;
private final int[] indexes = new int[attributes.size()];
void update() {
this.size = 0;
Arrays.fill(this.indexes, -1);
for (int i = 0; i < MirrorSets.this.assigned.length; i++) {
if (MirrorSets.this.assigned[i] == this) {
this.indexes[this.size] = i;
this.size++;
}
}
}
<A> int resolve(@Nullable Object source, @Nullable A annotation,
BiFunction<Method, Object, Object> valueExtractor) {
int result = -1;
Object lastValue = null;
for (int i = 0; i < this.size; i++) {
Method attribute = attributes.get(this.indexes[i]);
Object value = valueExtractor.apply(attribute, annotation);
boolean isDefaultValue = value == null || isEquivalentToDefaultValue(
attribute, value, valueExtractor);
if (isDefaultValue || ObjectUtils.nullSafeEquals(lastValue, value)) {
continue;
}
if (lastValue != null &&
!ObjectUtils.nullSafeEquals(lastValue, value)) {
String on = (source != null) ? " declared on " + source : "";
throw new AnnotationConfigurationException(String.format(
"Different @AliasFor mirror values for annotation [%s]%s, "
+ "attribute '%s' and its alias '%s' are declared with values of [%s] and [%s].",
getAnnotationType().getName(), on,
attributes.get(result).getName(),
attribute.getName(),
ObjectUtils.nullSafeToString(lastValue),
ObjectUtils.nullSafeToString(value)));
}
result = this.indexes[i];
lastValue = value;
}
return result;
}
int size() {
return this.size;
}
Method get(int index) {
int attributeIndex = this.indexes[index];
return attributes.get(attributeIndex);
}
int getAttributeIndex(int index) {
return this.indexes[index];
}
}
}
}

View File

@@ -0,0 +1,239 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* Provides {@link AnnotationTypeMapping} information for a single source
* annotation type. Performs a recursive breadth first crawl of all
* meta-annotations to ultimately provide a quick way to map the attributes of
* root {@link Annotation}.
*
* <p>Supports convention based merging of meta-annotations as well as implicit
* and explicit {@link AliasFor @AliasFor} aliases. Also provide information
* about mirrored attributes.
*
* <p>This class is designed to be cached so that meta-annotations only need to
* be searched once, regardless of how many times they are actually used.
*
* @author Phillip Webb
* @since 5.2
* @see AnnotationTypeMapping
*/
final class AnnotationTypeMappings {
private static final IntrospectionFailureLogger failureLogger = IntrospectionFailureLogger.DEBUG;
private static final Map<AnnotationFilter, Cache> cache = new ConcurrentReferenceHashMap<>();
private final AnnotationFilter filter;
private final List<AnnotationTypeMapping> mappings;
private AnnotationTypeMappings(AnnotationFilter filter,
Class<? extends Annotation> annotationType) {
this.filter = filter;
this.mappings = new ArrayList<>();
addAllMappings(annotationType);
this.mappings.forEach(AnnotationTypeMapping::afterAllMappingsSet);
}
private void addAllMappings(Class<? extends Annotation> annotationType) {
Deque<AnnotationTypeMapping> queue = new ArrayDeque<>();
addIfPossible(queue, null, annotationType, null);
while (!queue.isEmpty()) {
AnnotationTypeMapping mapping = queue.removeFirst();
this.mappings.add(mapping);
addMetaAnnotationsToQueue(queue, mapping);
}
}
private void addMetaAnnotationsToQueue(Deque<AnnotationTypeMapping> queue,
AnnotationTypeMapping parent) {
Annotation[] metaAnnotations = AnnotationsScanner.getDeclaredAnnotations(
parent.getAnnotationType(), false);
for (Annotation metaAnnotation : metaAnnotations) {
if (!isMappable(parent, metaAnnotation)) {
continue;
}
Annotation[] repeatedAnnotations = RepeatableContainers.standardRepeatables()
.findRepeatedAnnotations(metaAnnotation);
if (repeatedAnnotations != null) {
for (Annotation repeatedAnnotation : repeatedAnnotations) {
if (!isMappable(parent, metaAnnotation)) {
continue;
}
addIfPossible(queue, parent, repeatedAnnotation);
}
}
else {
addIfPossible(queue, parent, metaAnnotation);
}
}
}
private void addIfPossible(Deque<AnnotationTypeMapping> queue,
AnnotationTypeMapping parent, Annotation annotation) {
addIfPossible(queue, parent, annotation.annotationType(), annotation);
}
private void addIfPossible(Deque<AnnotationTypeMapping> queue,
@Nullable AnnotationTypeMapping parent,
Class<? extends Annotation> annotationType, @Nullable Annotation annotation) {
try {
queue.addLast(new AnnotationTypeMapping(parent, annotationType, annotation));
}
catch (Exception ex) {
if (ex instanceof AnnotationConfigurationException) {
throw (AnnotationConfigurationException) ex;
}
if (failureLogger.isEnabled()) {
failureLogger.log(
"Failed to introspect meta-annotation "
+ annotationType.getName(),
(parent != null) ? parent.getAnnotationType() : null, ex);
}
}
}
private boolean isMappable(AnnotationTypeMapping parent, Annotation metaAnnotation) {
return !this.filter.matches(metaAnnotation) &&
!AnnotationFilter.PLAIN.matches(parent.getAnnotationType()) &&
!isAlreadyMapped(parent, metaAnnotation);
}
private boolean isAlreadyMapped(AnnotationTypeMapping parent,
Annotation metaAnnotation) {
Class<? extends Annotation> annotationType = metaAnnotation.annotationType();
AnnotationTypeMapping mapping = parent;
while (mapping != null) {
if (mapping.getAnnotationType().equals(annotationType)) {
return true;
}
mapping = mapping.getParent();
}
return false;
}
/**
* Return the total number of contained mappings.
* @return the total number of mappings
*/
int size() {
return this.mappings.size();
}
/**
* Return an individual mapping from this instance. Index {@code 0} will
* always be return the root mapping, higer indexes will return
* meta-annotation mappings.
* @param index the index to return
* @return the {@link AnnotationTypeMapping}
* @throws IndexOutOfBoundsException if the index is out of range
* (<tt>index &lt; 0 || index &gt;= size()</tt>)
*/
AnnotationTypeMapping get(int index) {
return this.mappings.get(index);
}
/**
* Return {@link AnnotationTypeMappings} for the specified annotation type.
* @param annotationType the source annotation type
* @return type mappings for the annotation type
*/
static AnnotationTypeMappings forAnnotationType(
Class<? extends Annotation> annotationType) {
return forAnnotationType(annotationType,
AnnotationFilter.mostAppropriateFor(annotationType));
}
/**
* Return {@link AnnotationTypeMappings} for the specified annotation type.
* @param annotationType the source annotation type
* @param annotationFilter the annotation filter used to limit which
* annotations are considered
* @return type mappings for the annotation type
*/
static AnnotationTypeMappings forAnnotationType(
Class<? extends Annotation> annotationType,
AnnotationFilter annotationFilter) {
Assert.notNull(annotationType, "AnnotationType must not be null");
Assert.notNull(annotationFilter, "AnnotationFilter must not be null");
return cache.computeIfAbsent(annotationFilter, Cache::new).get(annotationType);
}
static void clearCache() {
cache.clear();
}
/**
* Cache created per {@link AnnotationFilter}.
*/
private static class Cache {
private final AnnotationFilter filter;
private final Map<Class<? extends Annotation>, AnnotationTypeMappings> mappings;
/**
* Create a cache instance with the specified filter.
* @param filter the annotation filter
*/
Cache(AnnotationFilter filter) {
this.filter = filter;
this.mappings = new ConcurrentReferenceHashMap<>();
}
/**
* Return or create {@link AnnotationTypeMappings} for the specified
* annotation type.
* @param annotationType the annotation type
* @return a new or existing {@link AnnotationTypeMapping} instance
*/
AnnotationTypeMappings get(Class<? extends Annotation> annotationType) {
return this.mappings.computeIfAbsent(annotationType, this::createMappings);
}
AnnotationTypeMappings createMappings(
Class<? extends Annotation> annotationType) {
return new AnnotationTypeMappings(this.filter, annotationType);
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import org.springframework.lang.Nullable;
/**
* Callback interface used to process annotations.
*
* @param <C> the context type
* @param <R> the result type
* @author Phillip Webb
* @see AnnotationsScanner
* @see TypeMappedAnnotations
*/
@FunctionalInterface
interface AnnotationsProcessor<C, R> {
/**
* Called when an aggregate is about to be processed. This method may return
* a {@code non-null} result to short-circuit any further processing.
* @param context context information relevant to the processor
* @param aggregateIndex the aggregate index about to be processed
* @return a {@code non-null} result if no further processing is required
*/
@Nullable
default R doWithAggregate(C context, int aggregateIndex) {
return null;
}
/**
* Called when an array of annotations can be processed. This method may
* return a {@code non-null} result to short-circuit any further processing.
* @param context context information relevant to the processor
* @param aggregateIndex the aggregate index of the provided annotations
* @param source the original source of the annotations, if known
* @param annotations the annotations to process (this array may contain
* {@code null} elements)
* @return a {@code non-null} result if no further processing is required
*/
@Nullable
R doWithAnnotations(C context, int aggregateIndex, @Nullable Object source,
Annotation[] annotations);
/**
* Return the final result to be returned. By default this method returns
* the last process result.
* @param result the last early exit result, or {@code null}.
* @return the final result to be returned to the caller
*/
@Nullable
default R finish(@Nullable R result) {
return result;
}
}

View File

@@ -0,0 +1,552 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Map;
import java.util.function.BiPredicate;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.lang.Nullable;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
* Scanner to search for relevant annotations on the hierarchy of an
* {@link AnnotatedElement}.
*
* @author Phillip Webb
* @since 5.2
* @see AnnotationsProcessor
*/
abstract class AnnotationsScanner {
private static final Annotation[] NO_ANNOTATIONS = {};
private static final Method[] NO_METHODS = {};
private static final Map<AnnotatedElement, Annotation[]> declaredAnnotationCache =
new ConcurrentReferenceHashMap<>(256);
private static final Map<Class<?>, Method[]> baseTypeMethodsCache =
new ConcurrentReferenceHashMap<>(256);
private AnnotationsScanner() {
}
/**
* Scan the hierarchy of the specified element for relevant annotations and
* call the processor as required.
* @param context an optional context object that will be passed back to the
* processor
* @param source the source element to scan
* @param searchStrategy the search strategy to use
* @param processor the processor that receives the annotations
* @return the result of {@link AnnotationsProcessor#finish(Object)}
*/
@Nullable
static <C, R> R scan(C context, AnnotatedElement source,
SearchStrategy searchStrategy, AnnotationsProcessor<C, R> processor) {
return scan(context, source, searchStrategy, processor, null);
}
/**
* Scan the hierarchy of the specified element for relevant annotations and
* call the processor as required.
* @param context an optional context object that will be passed back to the
* processor
* @param source the source element to scan
* @param searchStrategy the search strategy to use
* @param processor the processor that receives the annotations
* @param classFilter an optional filter that can be used to entirely filter
* out a specific class from the hierarchy
* @return the result of {@link AnnotationsProcessor#finish(Object)}
*/
@Nullable
static <C, R> R scan(C context, AnnotatedElement source,
SearchStrategy searchStrategy, AnnotationsProcessor<C, R> processor,
@Nullable BiPredicate<C, Class<?>> classFilter) {
R result = process(context, source, searchStrategy, processor, classFilter);
return processor.finish(result);
}
@Nullable
private static <C, R> R process(C context, AnnotatedElement source,
SearchStrategy searchStrategy, AnnotationsProcessor<C, R> processor,
@Nullable BiPredicate<C, Class<?>> classFilter) {
if (source instanceof Class) {
return processClass(context, (Class<?>) source, searchStrategy, processor, classFilter);
}
if (source instanceof Method) {
return processMethod(context, (Method) source, searchStrategy, processor, classFilter);
}
return processElement(context, source, processor, classFilter);
}
@Nullable
private static <C, R> R processClass(C context, Class<?> source,
SearchStrategy searchStrategy, AnnotationsProcessor<C, R> processor,
@Nullable BiPredicate<C, Class<?>> classFilter) {
switch (searchStrategy) {
case DIRECT:
return processElement(context, source,
processor, classFilter);
case INHERITED_ANNOTATIONS:
return processClassInheritedAnnotations(context, source,
processor, classFilter);
case SUPER_CLASS:
return processClassHierarchy(context, new int[] { 0 }, source,
processor, classFilter, false);
case EXHAUSTIVE:
return processClassHierarchy(context, new int[] { 0 }, source,
processor, classFilter, true);
}
throw new IllegalStateException("Unsupported search strategy " + searchStrategy);
}
@Nullable
private static <C, R> R processClassInheritedAnnotations(C context, Class<?> source,
AnnotationsProcessor<C, R> processor,
@Nullable BiPredicate<C, Class<?>> classFilter) {
if (isWithoutHierarchy(source)) {
return processElement(context, source, processor, classFilter);
}
Annotation[] relevant = null;
int remaining = Integer.MAX_VALUE;
int aggregateIndex = 0;
Class<?> root = source;
while (source != null && source != Object.class
&& !hasPlainJavaAnnotationsOnly(source) && remaining > 0) {
R result = processor.doWithAggregate(context, aggregateIndex);
if (result != null) {
return result;
}
if (isFiltered(source, context, classFilter)) {
continue;
}
Annotation[] declaredAnnotations =
getDeclaredAnnotations(context, source, classFilter, true);
if (relevant == null && declaredAnnotations.length > 0) {
relevant = root.getAnnotations();
remaining = relevant.length;
}
for (int i = 0; i < declaredAnnotations.length; i++) {
if (declaredAnnotations[i] != null) {
boolean isRelevant = false;
for (int relevantIndex = 0; relevantIndex < relevant.length; relevantIndex++) {
if (relevant[relevantIndex] != null &&
declaredAnnotations[i].annotationType() == relevant[relevantIndex].annotationType()) {
isRelevant = true;
relevant[relevantIndex] = null;
remaining--;
break;
}
}
if (!isRelevant) {
declaredAnnotations[i] = null;
}
}
}
result = processor.doWithAnnotations(context, aggregateIndex, source, declaredAnnotations);
if (result != null) {
return result;
}
source = source.getSuperclass();
aggregateIndex++;
}
return null;
}
@Nullable
private static <C, R> R processClassHierarchy(C context, int[] aggregateIndex,
Class<?> source, AnnotationsProcessor<C, R> processor,
@Nullable BiPredicate<C, Class<?>> classFilter, boolean includeInterfaces) {
R result = processor.doWithAggregate(context, aggregateIndex[0]);
if (result != null) {
return result;
}
if (hasPlainJavaAnnotationsOnly(source)) {
return null;
}
Annotation[] annotations = getDeclaredAnnotations(context, source, classFilter, false);
result = processor.doWithAnnotations(context, aggregateIndex[0], source, annotations);
if (result != null) {
return result;
}
aggregateIndex[0]++;
if (includeInterfaces) {
for (Class<?> interfaceType : source.getInterfaces()) {
R interfacesResult = processClassHierarchy(context, aggregateIndex,
interfaceType, processor, classFilter, includeInterfaces);
if (interfacesResult != null) {
return interfacesResult;
}
}
}
Class<?> superclass = source.getSuperclass();
if (superclass != Object.class && superclass != null) {
R superclassResult = processClassHierarchy(context, aggregateIndex,
superclass, processor, classFilter, includeInterfaces);
if (superclassResult != null) {
return superclassResult;
}
}
return null;
}
@Nullable
private static <C, R> R processMethod(C context, Method source,
SearchStrategy searchStrategy, AnnotationsProcessor<C, R> processor,
@Nullable BiPredicate<C, Class<?>> classFilter) {
switch (searchStrategy) {
case DIRECT:
case INHERITED_ANNOTATIONS:
return processMethodInheritedAnnotations(context, source,
processor, classFilter);
case SUPER_CLASS:
return processMethodHierarchy(context, new int[] { 0 }, source.getDeclaringClass(),
processor, classFilter, source, false);
case EXHAUSTIVE:
return processMethodHierarchy(context, new int[] { 0 }, source.getDeclaringClass(),
processor, classFilter, source, true);
}
throw new IllegalStateException("Unsupported search strategy " + searchStrategy);
}
@Nullable
private static <C, R> R processMethodInheritedAnnotations(C context, Method source,
AnnotationsProcessor<C, R> processor,
@Nullable BiPredicate<C, Class<?>> classFilter) {
R result = processor.doWithAggregate(context, 0);
return result != null ? result :
processMethodAnnotations(context, 0, source, processor, classFilter);
}
@Nullable
private static <C, R> R processMethodHierarchy(C context, int[] aggregateIndex,
Class<?> sourceClass, AnnotationsProcessor<C, R> processor,
@Nullable BiPredicate<C, Class<?>> classFilter, Method rootMethod,
boolean includeInterfaces) {
R result = processor.doWithAggregate(context, aggregateIndex[0]);
if (result != null) {
return result;
}
if (hasPlainJavaAnnotationsOnly(sourceClass)) {
return null;
}
boolean calledProcessor = false;
if (sourceClass == rootMethod.getDeclaringClass()) {
result = processMethodAnnotations(context, aggregateIndex[0],
rootMethod, processor, classFilter);
calledProcessor = true;
if (result != null) {
return result;
}
}
else {
for (Method candidateMethod : getBaseTypeMethods(context, sourceClass, classFilter)) {
if (candidateMethod != null && isOverride(rootMethod, candidateMethod)) {
result = processMethodAnnotations(context, aggregateIndex[0],
candidateMethod, processor, classFilter);
calledProcessor = true;
if (result != null) {
return result;
}
}
}
}
if (Modifier.isPrivate(rootMethod.getModifiers())) {
return null;
}
if (calledProcessor) {
aggregateIndex[0]++;
}
if (includeInterfaces) {
for (Class<?> interfaceType : sourceClass.getInterfaces()) {
R interfacesResult = processMethodHierarchy(context, aggregateIndex,
interfaceType, processor, classFilter, rootMethod, includeInterfaces);
if (interfacesResult != null) {
return interfacesResult;
}
}
}
Class<?> superclass = sourceClass.getSuperclass();
if (superclass != Object.class && superclass != null) {
R superclassResult = processMethodHierarchy(context, aggregateIndex,
superclass, processor, classFilter, rootMethod, includeInterfaces);
if (superclassResult != null) {
return superclassResult;
}
}
return null;
}
private static <C> Method[] getBaseTypeMethods(C context, Class<?> baseType,
@Nullable BiPredicate<C, Class<?>> classFilter) {
if (baseType == Object.class || hasPlainJavaAnnotationsOnly(baseType) ||
isFiltered(baseType, context, classFilter)) {
return NO_METHODS;
}
Method[] methods = baseTypeMethodsCache.get(baseType);
if (methods == null) {
boolean isInterface = baseType.isInterface();
methods = isInterface ? baseType.getMethods()
: ReflectionUtils.getDeclaredMethods(baseType);
int cleared = 0;
for (int i = 0; i < methods.length; i++) {
if ((!isInterface && Modifier.isPrivate(methods[i].getModifiers())) ||
hasPlainJavaAnnotationsOnly(methods[i]) ||
getDeclaredAnnotations(methods[i], false).length == 0) {
methods[i] = null;
cleared++;
}
}
if (cleared == methods.length) {
methods = NO_METHODS;
}
baseTypeMethodsCache.put(baseType, methods);
}
return methods;
}
private static boolean isOverride(Method rootMethod, Method candidateMethod) {
return !Modifier.isPrivate(candidateMethod.getModifiers()) &&
candidateMethod.getName().equals(rootMethod.getName()) &&
hasSameParameterTypes(rootMethod, candidateMethod);
}
private static boolean hasSameParameterTypes(Method rootMethod,
Method candidateMethod) {
if (candidateMethod.getParameterCount() != rootMethod.getParameterCount()) {
return false;
}
Class<?>[] rootParameterTypes = rootMethod.getParameterTypes();
Class<?>[] candidateParameterTypes = candidateMethod.getParameterTypes();
if (Arrays.equals(candidateParameterTypes, rootParameterTypes)) {
return true;
}
return hasSameGenericTypeParameters(rootMethod, candidateMethod,
rootParameterTypes);
}
private static boolean hasSameGenericTypeParameters(Method rootMethod,
Method candidateMethod, Class<?>[] rootParameterTypes) {
Class<?> sourceDeclaringClass = rootMethod.getDeclaringClass();
Class<?> candidateDeclaringClass = candidateMethod.getDeclaringClass();
if (!candidateDeclaringClass.isAssignableFrom(sourceDeclaringClass)) {
return false;
}
for (int i = 0; i < rootParameterTypes.length; i++) {
Class<?> resolvedParameterType = ResolvableType.forMethodParameter(
candidateMethod, i, sourceDeclaringClass).resolve();
if (rootParameterTypes[i] != resolvedParameterType) {
return false;
}
}
return true;
}
@Nullable
private static <C, R> R processMethodAnnotations(C context, int aggregateIndex,
Method source, AnnotationsProcessor<C, R> processor,
@Nullable BiPredicate<C, Class<?>> classFilter) {
Annotation[] annotations = getDeclaredAnnotations(context, source, classFilter,
false);
R result = processor.doWithAnnotations(context, aggregateIndex, source,
annotations);
if (result != null) {
return result;
}
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(source);
if (bridgedMethod != source) {
Annotation[] bridgedAnnotations = getDeclaredAnnotations(context,
bridgedMethod, classFilter, true);
for (int i = 0; i < bridgedAnnotations.length; i++) {
if (ObjectUtils.containsElement(annotations, bridgedAnnotations[i])) {
bridgedAnnotations[i] = null;
}
}
return processor.doWithAnnotations(context, aggregateIndex, source,
bridgedAnnotations);
}
return null;
}
@Nullable
private static <C, R> R processElement(C context, AnnotatedElement source,
AnnotationsProcessor<C, R> processor,
@Nullable BiPredicate<C, Class<?>> classFilter) {
R result = processor.doWithAggregate(context, 0);
return result != null ? result
: processor.doWithAnnotations(context, 0, source,
getDeclaredAnnotations(context, source, classFilter, false));
}
private static <C, R> Annotation[] getDeclaredAnnotations(C context,
AnnotatedElement source, @Nullable BiPredicate<C, Class<?>> classFilter,
boolean copy) {
if (source instanceof Class &&
isFiltered((Class<?>) source, context, classFilter)) {
return NO_ANNOTATIONS;
}
if (source instanceof Method &&
isFiltered(((Method) source).getDeclaringClass(), context, classFilter)) {
return NO_ANNOTATIONS;
}
return getDeclaredAnnotations(source, copy);
}
@SuppressWarnings("unchecked")
@Nullable
static <A extends Annotation> A getDeclaredAnnotation(AnnotatedElement source,
Class<A> annotationType) {
Annotation[] annotations = getDeclaredAnnotations(source, false);
for (int i = 0; i < annotations.length; i++) {
if (annotations[i] != null &&
annotationType.equals(annotations[i].annotationType())) {
return (A) annotations[i];
}
}
return null;
}
static Annotation[] getDeclaredAnnotations(AnnotatedElement source,
boolean defensive) {
boolean cached = true;
Annotation[] annotations = declaredAnnotationCache.get(source);
if (annotations == null) {
annotations = source.getDeclaredAnnotations();
if (annotations.length != 0) {
boolean allIgnored = true;
for (int i = 0; i < annotations.length; i++) {
Annotation annotation = annotations[i];
if (isIgnorable(annotation.annotationType()) ||
!AttributeMethods.forAnnotationType(
annotation.annotationType()).isValid(annotation)) {
annotations[i] = null;
}
else {
allIgnored = false;
}
}
annotations = allIgnored ? NO_ANNOTATIONS : annotations;
if (source instanceof Class || source instanceof Member) {
declaredAnnotationCache.put(source, annotations);
cached = true;
}
}
}
if (!defensive || annotations.length == 0 || !cached) {
return annotations;
}
return annotations.clone();
}
private static boolean isIgnorable(Class<?> annotationType) {
return (annotationType == Nullable.class ||
annotationType == FunctionalInterface.class);
}
private static <C> boolean isFiltered(Class<?> sourceClass, C context,
@Nullable BiPredicate<C, Class<?>> classFilter) {
return classFilter != null && classFilter.test(context, sourceClass);
}
static boolean isKnownEmpty(AnnotatedElement source,
SearchStrategy searchStrategy, AnnotationFilter annotationFilter) {
if (annotationFilter == AnnotationFilter.PLAIN &&
hasPlainJavaAnnotationsOnly(source)) {
return true;
}
if (searchStrategy == SearchStrategy.DIRECT || isWithoutHierarchy(source)) {
if (source instanceof Method && ((Method) source).isBridge()) {
return false;
}
return getDeclaredAnnotations(source, false).length == 0;
}
return false;
}
static boolean hasPlainJavaAnnotationsOnly(@Nullable Object annotatedElement) {
Class<?> type = null;
if (annotatedElement instanceof Class) {
type = (Class<?>) annotatedElement;
}
else if (annotatedElement instanceof Member) {
type = ((Member) annotatedElement).getDeclaringClass();
}
else {
return false;
}
String name = type.getName();
return type.equals(Ordered.class) ||
name.startsWith("java") ||
name.startsWith("org.springframework.lang.");
}
private static boolean isWithoutHierarchy(AnnotatedElement source) {
if (source == Object.class) {
return true;
}
if (source instanceof Class) {
Class<?> sourceClass = (Class<?>) source;
return sourceClass.getSuperclass() == Object.class &&
sourceClass.getInterfaces().length == 0;
}
if (source instanceof Method) {
Method sourceMethod = (Method) source;
return Modifier.isPrivate(sourceMethod.getModifiers()) ||
isWithoutHierarchy(sourceMethod.getDeclaringClass());
}
return true;
}
static void clearCache() {
declaredAnnotationCache.clear();
baseTypeMethodsCache.clear();
}
}

View File

@@ -0,0 +1,316 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* Provides a quick way to access the attribute methods of an {@link Annotation}
* with consistent ordering as well as a few useful utility methods.
*
* @author Phillip Webb
* @since 5.2
*/
final class AttributeMethods {
static final AttributeMethods NONE = new AttributeMethods(null, new Method[0]);
private static final Map<Class<? extends Annotation>, AttributeMethods> cache =
new ConcurrentReferenceHashMap<>();
private static final Comparator<Method> methodComparator = (m1, m2) -> {
if (m1 != null && m2 != null) {
return m1.getName().compareTo(m2.getName());
}
return m1 != null ? -1 : 1;
};
@Nullable
private final Class<? extends Annotation> annotationType;
private final Method[] attributeMethods;
private final boolean[] canThrowTypeNotPresentException;
private final boolean hasDefaultValueMethod;
private final boolean hasNestedAnnotation;
private AttributeMethods(@Nullable Class<? extends Annotation> annotationType,
Method[] attributeMethods) {
this.annotationType = annotationType;
this.attributeMethods = attributeMethods;
this.canThrowTypeNotPresentException = new boolean[attributeMethods.length];
boolean foundDefaultValueMethod = false;
boolean foundNestedAnnotation = false;
for (int i = 0; i < attributeMethods.length; i++) {
Method method = this.attributeMethods[i];
Class<?> type = method.getReturnType();
if (method.getDefaultValue() != null) {
foundDefaultValueMethod = true;
}
if (type.isAnnotation() ||
(type.isArray() && type.getComponentType().isAnnotation())) {
foundNestedAnnotation = true;
}
method.setAccessible(true);
this.canThrowTypeNotPresentException[i] =
type == Class.class ||
type == Class[].class ||
type.isEnum();
}
this.hasDefaultValueMethod = foundDefaultValueMethod;
this.hasNestedAnnotation = foundNestedAnnotation;
}
/**
* Return if this instance only contains only a single attribute named
* {@code value}.
* @return {@code true} if this is only a value attribute
*/
boolean isOnlyValueAttribute() {
return this.attributeMethods.length == 1 &&
MergedAnnotation.VALUE.equals(this.attributeMethods[0].getName());
}
/**
* Returns {@code true} if values from the given annotation can be safely
* accessed without causing any {@link TypeNotPresentException
* TypeNotPresentExceptions}.
* @param annotation the annotation to check
* @return {@true} if all values are present
* @see #validate(Annotation)
*/
boolean isValid(Annotation annotation) {
assertAnnotation(annotation);
for (int i = 0; i < size(); i++) {
if (canThrowTypeNotPresentException(i)) {
try {
get(i).invoke(annotation);
}
catch (Throwable ex) {
return false;
}
}
}
return true;
}
/**
* Checks if values from the given annotation can be safely accessed without
* causing any {@link TypeNotPresentException TypeNotPresentExceptions}. In
* particular, this method is designed to cover Google App Engine's late
* arrival of such exceptions for {@code Class} values (instead of the more
* typical early {@code Class.getAnnotations() failure}.
* @param annotation the annotation to validate
* @throws IllegalStateException if a declared {@code Class} attribute could
* not be read
* @see #isValid(Annotation)
*/
void validate(Annotation annotation) {
assertAnnotation(annotation);
for (int i = 0; i < size(); i++) {
if (canThrowTypeNotPresentException(i)) {
try {
get(i).invoke(annotation);
}
catch (Throwable ex) {
throw new IllegalStateException(
"Could not obtain annotation attribute value for "
+ get(i).getName() + " declared on "
+ annotation.annotationType(),
ex);
}
}
}
}
private void assertAnnotation(Annotation annotation) {
Assert.notNull(annotation, "Annotation must not be null");
if (this.annotationType != null) {
Assert.isInstanceOf(this.annotationType, annotation);
}
}
/**
* Return the attribute with the specified name or {@code null} if no
* matching attribute exists.
* @param name the attribute name to find
* @return the attribute method or {@code null}
*/
@Nullable
Method get(String name) {
int index = indexOf(name);
return index != -1 ? this.attributeMethods[index] : null;
}
/**
* Return the attribute at the specified index.
* @param index the index of the attribute to return
* @return the attribute method
* @throws IndexOutOfBoundsException if the index is out of range
* (<tt>index &lt; 0 || index &gt;= size()</tt>)
*/
Method get(int index) {
return this.attributeMethods[index];
}
/**
* Return {@code true} if the attribute at the specified index could throw a
* {@link TypeNotPresentException} when accessed.
* @param index the index of the attribute to check
* @return {@code true} if the attribute can throw a
* {@link TypeNotPresentException}
*/
boolean canThrowTypeNotPresentException(int index) {
return this.canThrowTypeNotPresentException[index];
}
/**
* Return the index of the attribute with the specified name, or {@code -1}
* if there is no attribute with the name.
* @param name the name to find
* @return the index of the attribute, or {@code -1}
*/
int indexOf(String name) {
for (int i = 0; i < this.attributeMethods.length; i++) {
if (this.attributeMethods[i].getName().equals(name)) {
return i;
}
}
return -1;
}
/**
* Return the index of the specified attribute , or {@code -1} if the
* attribute is not not in this collection.
* @param attribute the attribute to find
* @return the index of the attribute, or {@code -1}
*/
int indexOf(Method attribute) {
for (int i = 0; i < this.attributeMethods.length; i++) {
if (this.attributeMethods[i].equals(attribute)) {
return i;
}
}
return -1;
}
/**
* Return the number of attributes in this collection.
* @return the number of attributes
*/
int size() {
return this.attributeMethods.length;
}
/**
* Return if at least one of the attribute methods has a default value.
* @return if there is at least one attribute method with a default value
*/
boolean hasDefaultValueMethod() {
return this.hasDefaultValueMethod;
}
/**
* Return if at least on of the attribute methods is a nested annotation.
* @return if there is at least one attribute method with a annotation type
*/
boolean hasNestedAnnotation() {
return this.hasNestedAnnotation;
}
/**
* Return the attribute methods for the given annotation type.
* @param annotationType the annotation type
* @return the attribute methods for the annotation
*/
static AttributeMethods forAnnotationType(
@Nullable Class<? extends Annotation> annotationType) {
if (annotationType == null) {
return NONE;
}
return cache.computeIfAbsent(annotationType, AttributeMethods::compute);
}
private static AttributeMethods compute(Class<? extends Annotation> annotationType) {
Method[] methods = annotationType.getDeclaredMethods();
int size = methods.length;
for (int i = 0; i < methods.length; i++) {
if (!isAttributeMethod(methods[i])) {
methods[i] = null;
size--;
}
}
if (size == 0) {
return NONE;
}
Arrays.sort(methods, methodComparator);
Method[] attributeMethods = new Method[size];
System.arraycopy(methods, 0, attributeMethods, 0, size);
return new AttributeMethods(annotationType, attributeMethods);
}
private static boolean isAttributeMethod(Method method) {
return method.getParameterCount() == 0 && method.getReturnType() != void.class;
}
/**
* Return a description for the given attribute method suitable to use in
* exception messages and logs.
* @param attribute the attribute to describe
* @return a description of the attribute
*/
static String describe(@Nullable Method attribute) {
if (attribute == null) {
return "(none)";
}
return describe(attribute.getDeclaringClass(), attribute.getName());
}
/**
* Return a description for the given attribute method suitable to use in
* exception messages and logs.
* @param annotationType the annotation type
* @param attributeName the attribute name
* @return a description of the attribute
*/
static String describe(@Nullable Class<?> annotationType,
@Nullable String attributeName) {
if (attributeName == null) {
return "(none)";
}
String in = annotationType != null ?
" in annotation [" + annotationType.getName() + "]" :
"";
return "attribute '" + attributeName + "'" + in;
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
/**
* Log facade used to handle annotation introspection failures (in particular
* {@code TypeNotPresentExceptions}). Allows annotation processing to continue,
* assuming that when Class attribute values are not resolvable the annotation
* should effectively disappear.
*
* @author Phillip Webb
* @since 5.2
*/
enum IntrospectionFailureLogger {
DEBUG {
@Override
public boolean isEnabled() {
return getLogger().isDebugEnabled();
}
@Override
public void log(String message) {
getLogger().debug(message);
}
},
INFO {
@Override
public boolean isEnabled() {
return getLogger().isInfoEnabled();
}
@Override
public void log(String message) {
getLogger().info(message);
}
};
@Nullable
private static Log logger;
abstract boolean isEnabled();
void log(String message, @Nullable Object source, Exception ex) {
String on = source != null ? " on " + source : "";
log(message + on + ": " + ex);
}
abstract void log(String message);
private static Log getLogger() {
Log logger = IntrospectionFailureLogger.logger;
if (logger == null) {
logger = LogFactory.getLog(MergedAnnotation.class);
IntrospectionFailureLogger.logger = logger;
}
return logger;
}
}

View File

@@ -0,0 +1,597 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Proxy;
import java.util.EnumSet;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.lang.Nullable;
/**
* A single merged annotation returned from a {@link MergedAnnotations}
* collection. Presents a view onto an annotation where attribute values may
* have been "merged" from different source values.
*
* <p>Attribute values may be accessed using the various {@code get} methods.
* For example, to access an {@code int} attribute the {@link #getInt(String)}
* method would be used.
*
* <p>Note that attribute values are <b>not</b> converted when accessed. For
* example, it is not possible to call {@link #getString(String)} if the
* underlying attribute is an {@code int}. The only exception to this rule is
* {@code Class} and {@code Class[]} values which may be accessed as
* {@code String} and {@code String[]} respectively to prevent potential early
* class initialization.
*
* <p>If necessary, a {@link MergedAnnotation} can be {@link #synthesize()
* synthesized} back into an actual {@link java.lang.annotation.Annotation}.
*
* @author Phillip Webb
* @since 5.2
* @param <A> the annotation type
* @see MergedAnnotations
* @see MergedAnnotationPredicates
*/
public interface MergedAnnotation<A extends Annotation> {
/**
* The attribute name for annotations with a single element.
*/
String VALUE = "value";
/**
* Return the class name of the actual annotation type.
* @return the annotation type
*/
String getType();
/**
* Return if the annotation is present on the source. Considers
* {@link #isDirectlyPresent() direct annotations}, and
* {@link #isMetaPresent() meta-annotation} annotations within the context
* of the {@link SearchStrategy} used.
* @return {@code true} if the annotation is present
*/
boolean isPresent();
/**
* Return if the annotation is directly present on the source. A directly
* present annotation is one that the user has explicitly defined and not
* one that is {@link #isMetaPresent() meta-present} or
* {@link Inherited @Inherited}.
* @return {@code true} if the annotation is directly present
*/
boolean isDirectlyPresent();
/**
* Return if the annotation is meta-present on the source. A meta-present
* annotation is an annotation that the user hasn't explicitly defined, but
* has been used as a meta-annotation somewhere in the annotation hierarchy.
* @return {@code true} if the annotation is meta-present
*/
boolean isMetaPresent();
/**
* Return the depth of this annotation related to its use as a
* meta-annotation. A directly declared annotation has a depth of {@code 0},
* a meta-annotation has a depth of {@code 1}, a meta-annotation on a
* meta-annotation has a depth of {@code 2}, etc. A {@link #missing()
* missing} annotation will always return a depth of {@code -1}.
* @return the annotation depth or {@code -1} if the annotation is missing
*/
int getDepth();
/**
* Return the index of the aggregate collection containing this annotation.
* Can be used to reorder a stream of annotations, for example, to give a
* higher priority to annotations declared on a superclass or interface. A
* {@link #missing() missing} annotation will always return an aggregate
* index of {@code -1}.
* @return the aggregate index (starting at {@code 0}) or {@code -1} if the
* annotation is missing
*/
int getAggregateIndex();
/**
* Return the source that ultimately declared the annotation, or
* {@code null} if the source is not known. If this merged annotation was
* created {@link MergedAnnotations#from(java.lang.reflect.AnnotatedElement)
* from} an {@link AnnotatedElement} then this source will be an element of
* the same type. If the annotation was loaded without using reflection, the
* source can be of any type, but should have a sensible {@code toString()}.
* Meta-annotations will return the same source as the {@link #getParent()}.
* @return the source, or {@code null}
*/
@Nullable
Object getSource();
/**
* Return the parent of the meta-annotation, or {@code null} if the
* annotation is not {@link #isMetaPresent() meta-present}.
* @return the parent annotation or {@code null}
*/
@Nullable
MergedAnnotation<?> getParent();
/**
* Return if the specified attribute name as a non-default value when
* compared to the annotation declaration.
* @param attributeName the attribute name
* @return {@code true} if the attribute value is different from the default
* value
*/
boolean hasNonDefaultValue(String attributeName);
/**
* Return if the specified attribute name as a default value when compared
* to the annotation declaration.
* @param attributeName the attribute name
* @return {@code true} if the attribute value is the same as the default
* value
*/
boolean hasDefaultValue(String attributeName) throws NoSuchElementException;
/**
* Return a required byte attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a byte
* @throws NoSuchElementException if there is no matching attribute
*/
byte getByte(String attributeName) throws NoSuchElementException;
/**
* Return a required byte array attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a byte array
* @throws NoSuchElementException if there is no matching attribute
*/
byte[] getByteArray(String attributeName) throws NoSuchElementException;
/**
* Return a required boolean attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a boolean
* @throws NoSuchElementException if there is no matching attribute
*/
boolean getBoolean(String attributeName) throws NoSuchElementException;
/**
* Return a required boolean array attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a boolean array
* @throws NoSuchElementException if there is no matching attribute
*/
boolean[] getBooleanArray(String attributeName) throws NoSuchElementException;
/**
* Return a required char attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a char
* @throws NoSuchElementException if there is no matching attribute
*/
char getChar(String attributeName) throws NoSuchElementException;
/**
* Return a required char array attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a char array
* @throws NoSuchElementException if there is no matching attribute
*/
char[] getCharArray(String attributeName) throws NoSuchElementException;
/**
* Return a required short attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a short
* @throws NoSuchElementException if there is no matching attribute
*/
short getShort(String attributeName) throws NoSuchElementException;
/**
* Return a required short array attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a short array
* @throws NoSuchElementException if there is no matching attribute
*/
short[] getShortArray(String attributeName) throws NoSuchElementException;
/**
* Return a required int attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as an int
* @throws NoSuchElementException if there is no matching attribute
*/
int getInt(String attributeName) throws NoSuchElementException;
/**
* Return a required int array attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as an int array
* @throws NoSuchElementException if there is no matching attribute
*/
int[] getIntArray(String attributeName) throws NoSuchElementException;
/**
* Return a required long attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a long
* @throws NoSuchElementException if there is no matching attribute
*/
long getLong(String attributeName) throws NoSuchElementException;
/**
* Return a required long array attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a long array
* @throws NoSuchElementException if there is no matching attribute
*/
long[] getLongArray(String attributeName) throws NoSuchElementException;
/**
* Return a required double attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a double
* @throws NoSuchElementException if there is no matching attribute
*/
double getDouble(String attributeName) throws NoSuchElementException;
/**
* Return a required double array attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a double array
* @throws NoSuchElementException if there is no matching attribute
*/
double[] getDoubleArray(String attributeName) throws NoSuchElementException;
/**
* Return a required float attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a float
* @throws NoSuchElementException if there is no matching attribute
*/
float getFloat(String attributeName) throws NoSuchElementException;
/**
* Return a required float array attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a float array
* @throws NoSuchElementException if there is no matching attribute
*/
float[] getFloatArray(String attributeName) throws NoSuchElementException;
/**
* Return a required string attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a string
* @throws NoSuchElementException if there is no matching attribute
*/
String getString(String attributeName) throws NoSuchElementException;
/**
* Return a required string array attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a string array
* @throws NoSuchElementException if there is no matching attribute
*/
String[] getStringArray(String attributeName) throws NoSuchElementException;
/**
* Return a required class attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a class
* @throws NoSuchElementException if there is no matching attribute
*/
Class<?> getClass(String attributeName) throws NoSuchElementException;
/**
* Return a required class array attribute value from the annotation.
* @param attributeName the attribute name
* @return the value as a class array
* @throws NoSuchElementException if there is no matching attribute
*/
Class<?>[] getClassArray(String attributeName) throws NoSuchElementException;
/**
* Return a required enum attribute value from the annotation.
* @param attributeName the attribute name
* @param type the enum type
* @return the value as a enum
* @throws NoSuchElementException if there is no matching attribute
*/
<E extends Enum<E>> E getEnum(String attributeName, Class<E> type)
throws NoSuchElementException;
/**
* Return a required enum array attribute value from the annotation.
* @param attributeName the attribute name
* @param type the enum type
* @return the value as a enum array
* @throws NoSuchElementException if there is no matching attribute
*/
<E extends Enum<E>> E[] getEnumArray(String attributeName, Class<E> type)
throws NoSuchElementException;
/**
* Return a required annotation attribute value from the annotation.
* @param attributeName the attribute name
* @param type the annotation type
* @return the value as a {@link MergedAnnotation}
* @throws NoSuchElementException if there is no matching attribute
*/
<T extends Annotation> MergedAnnotation<T> getAnnotation(String attributeName,
Class<T> type) throws NoSuchElementException;
/**
* Return a required annotation array attribute value from the annotation.
* @param attributeName the attribute name
* @param type the annotation type
* @return the value as a {@link MergedAnnotation} array
* @throws NoSuchElementException if there is no matching attribute
*/
<T extends Annotation> MergedAnnotation<T>[] getAnnotationArray(String attributeName,
Class<T> type) throws NoSuchElementException;
/**
* Return an optional attribute value from the annotation.
* @param attributeName the attribute name
* @return an optional value or {@link Optional#empty()} if there is no
* matching attribute
*/
Optional<Object> getValue(String attributeName);
/**
* Return an optional attribute value from the annotation.
* @param attributeName the attribute name
* @param type the attribute type. Must be compatible with the underlying
* attribute type or {@code Object.class}.
* @return an optional value or {@link Optional#empty()} if there is no
* matching attribute
*/
<T> Optional<T> getValue(String attributeName, Class<T> type);
/**
* Return the default attribute value from the annotation as specified in
* the annotation declaration.
* @param attributeName the attribute name
* @return an optional of the default value or {@link Optional#empty()} if
* there is no matching attribute or no defined default
*/
Optional<Object> getDefaultValue(String attributeName);
/**
* Return the default attribute value from the annotation as specified in
* the annotation declaration.
* @param attributeName the attribute name
* @param type the attribute type. Must be compatible with the underlying
* attribute type or {@code Object.class}.
* @return an optional of the default value or {@link Optional#empty()} if
* there is no matching attribute or no defined default
*/
<T> Optional<T> getDefaultValue(String attributeName, Class<T> type);
/**
* Return a new view of the annotation with all attributes that have default
* values removed.
* @return a filtered view of the annotation without any attributes that
* have a default value
* @see #filterAttributes(Predicate)
*/
MergedAnnotation<A> filterDefaultValues();
/**
* Return a new view of the annotation with only attributes that match the
* given predicate.
* @param predicate a predicate used to filter attribute names
* @return a filtered view of the annotation
* @see #filterDefaultValues()
* @see MergedAnnotationPredicates
*/
MergedAnnotation<A> filterAttributes(Predicate<String> predicate);
/**
* Return a new view of the annotation that exposes non-merged attribute
* values. Methods from this view will return attribute values with only
* alias mirroring rules applied. Aliases to parent attributes will not be
* applied.
* @return a non-merged view of the annotation
*/
MergedAnnotation<A> withNonMergedAttributes();
/**
* Return 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
* @return a map containing the attributes and values
*/
Map<String, Object> asMap(MapValues... options);
/**
* Return a {@link Map} of the supplied type that contains all the
* annotation attributes. The {@link MapValues} options may be used to
* change the way that values are added.
* @param factory a map factory or {@code null} to return an immutable map.
* @param options map value options
* @return a map containing the attributes and values
*/
<T extends Map<String, Object>> T asMap(
@Nullable Function<MergedAnnotation<?>, T> factory, MapValues... options);
/**
* Return a type-safe synthesized version of this annotation that can be
* used directly in code. The result is synthesized using a JDK
* {@link Proxy} and as a result may incur a computational cost when first
* invoked.
* @return a sythesized version of the annotation.
* @throws NoSuchElementException on a missing annotation
*/
A synthesize() throws NoSuchElementException;
/**
* Optionally return type-safe synthesized version of this annotation based
* on a condition predicate. The result is synthesized using a JDK
* {@link Proxy} and as a result may incur a computational cost when first
* invoked.
* @param condition the test to determine if the annotation can be
* sythesized
* @return a optional containing the sythesized version of the annotation or
* an empty optional if the condition doesn't match
* @throws NoSuchElementException on a missing annotation
* @see MergedAnnotationPredicates
*/
Optional<A> synthesize(@Nullable Predicate<? super MergedAnnotation<A>> condition)
throws NoSuchElementException;
/**
* Return an {@link MergedAnnotation} that represents a missing annotation
* (i.e. one that is not present).
* @return an instance representing a missing annotation
*/
static <A extends Annotation> MergedAnnotation<A> missing() {
return MissingMergedAnnotation.getInstance();
}
/**
* Create a new {@link MergedAnnotation} instance from the specified
* annotation.
* @param annotation the annotation to include
* @return a {@link MergedAnnotation} instance containing the annotation
*/
static <A extends Annotation> MergedAnnotation<A> from(A annotation) {
return from(null, annotation);
}
/**
* Create a new {@link MergedAnnotations} instance from the specified
* annotation.
* @param source the source for the annotation. This source is used only for
* information and logging. It does not need to <em>actually</em> contain
* the specified annotations and it will not be searched.
* @param annotation the annotation to include
* @return a {@link MergedAnnotation} instance for the annotation
*/
static <A extends Annotation> MergedAnnotation<A> from(@Nullable Object source,
A annotation) {
return TypeMappedAnnotation.from(source, annotation);
}
/**
* Create a new {@link MergedAnnotations} instance from the specified
* annotation type. The resulting annotation will not have any attribute
* values, but may still be used to query default values.
* @param annotationType the annotation type
* @return a {@link MergedAnnotation} instance for the annotation
*/
static <A extends Annotation> MergedAnnotation<A> from(Class<A> annotationType) {
return from(null, annotationType, null);
}
/**
* Create a new {@link MergedAnnotations} instance from the specified
* annotation type and attributes maps.
* @param annotationType the annotation type
* @param attributes the annotation attributes or {@code null} if just
* default values should be used
* @return a {@link MergedAnnotation} instance for the annotation and
* attributes
* @see #from(AnnotatedElement, Class, Map)
*/
static <A extends Annotation> MergedAnnotation<A> from(Class<A> annotationType,
@Nullable Map<String, ?> attributes) {
return from(null, annotationType, attributes);
}
/**
* Create a new {@link MergedAnnotations} instance from the specified
* annotation type and attributes maps.
* @param source the source for the annotation. This source is used only for
* information and logging. It does not need to <em>actually</em> contain
* the specified annotations and it will not be searched.
* @param annotationType the annotation type
* @param attributes the annotation attributes or {@code null} if just
* default values should be used
* @return a {@link MergedAnnotation} instance for the annotation and
* attributes
*/
static <A extends Annotation> MergedAnnotation<A> from(
@Nullable AnnotatedElement source, Class<A> annotationType,
@Nullable Map<String, ?> attributes) {
return TypeMappedAnnotation.from(source, annotationType, attributes);
}
/**
* Options that effect the way map values are
* {@link MergedAnnotation#asMap(MapValues...) converted}.
*/
enum MapValues {
/**
* Add class or class array attributes as strings.
*/
CLASS_TO_STRING,
/**
* Convert any 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) {
if (candidate == this) {
return true;
}
}
return false;
}
/**
* 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
*/
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]);
}
private static <T> void addIfTrue(Set<T> result, T value, boolean test) {
if (test) {
result.add(value);
}
}
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
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.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Collector implementations that provide various reduction operations for
* {@link MergedAnnotation MergedAnnotations}.
*
* @author Phillip Webb
* @since 5.2
*/
public abstract class MergedAnnotationCollectors {
private static final Characteristics[] NO_CHARACTERISTICS = {};
private static final Characteristics[] IDENTITY_FINISH_CHARACTERISTICS = {
Characteristics.IDENTITY_FINISH };
private MergedAnnotationCollectors() {
}
/**
* Returns a new {@link Collector} that accumulates merged annotations to a
* {@link LinkedHashSet} containing {@link MergedAnnotation#synthesize()
* synthesized} versions.
* @param <A> the annotation type
* @return a {@link Collector} which collects and synthesizes the
* annotations into a {@link Set}
*/
public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, Set<A>> toAnnotationSet() {
return Collector.of(ArrayList<A>::new, (list, annotation) -> list.add(annotation.synthesize()),
MergedAnnotationCollectors::addAll, LinkedHashSet::new);
}
/**
* Returns a new {@link Collector} that accumulates merged annotations to an
* {@link Annotation} array containing {@link MergedAnnotation#synthesize()
* synthesized} versions.
* @param <A> the annotation type
* @return a {@link Collector} which collects and synthesizes the
* annotations into an {@code Annotation[]}
* @see #toAnnotationArray(IntFunction)
*/
public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, Annotation[]> toAnnotationArray() {
return toAnnotationArray(Annotation[]::new);
}
/**
* Returns a new {@link Collector} that accumulates merged annotations to an
* {@link Annotation} array containing {@link MergedAnnotation#synthesize()
* synthesized} versions.
* @param <A> the annotation type
* @param <R> the resulting array type
* @param generator a function which produces a new array of the desired
* type and the provided length
* @return a {@link Collector} which collects and synthesizes the
* annotations into an annotation array
* @see #toAnnotationArray
*/
public static <R extends Annotation, A extends R> Collector<MergedAnnotation<A>, ?, R[]> toAnnotationArray(
IntFunction<R[]> generator) {
return Collector.of(ArrayList::new, (list, annotation) -> list.add(annotation.synthesize()),
MergedAnnotationCollectors::addAll, list -> list.toArray(generator.apply(list.size())));
}
/**
* Returns a new {@link Collector} that accumulates merged annotations to an
* {@link MultiValueMap} with items {@link MultiValueMap#add(Object, Object)
* added} from each merged annotation
* {@link MergedAnnotation#asMap(MapValues...) as a map}.
* @param <A> the annotation type
* @param options the map conversion options
* @return a {@link Collector} which collects and synthesizes the
* annotations into a {@link LinkedMultiValueMap}
* @see #toMultiValueMap(Function, MapValues...)
*/
public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, MultiValueMap<String, Object>> toMultiValueMap(
MapValues... options) {
return toMultiValueMap(Function.identity(), options);
}
/**
* Returns a new {@link Collector} that accumulates merged annotations to an
* {@link MultiValueMap} with items {@link MultiValueMap#add(Object, Object)
* added} from each merged annotation
* {@link MergedAnnotation#asMap(MapValues...) as a map}.
* @param <A> the annotation type
* @param options the map conversion options
* @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(MapValues...)
*/
public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, MultiValueMap<String, Object>> toMultiValueMap(
Function<MultiValueMap<String, Object>, MultiValueMap<String, Object>> finisher,
MapValues... options) {
Assert.notNull(finisher, "Finisher must not be null");
Characteristics[] characteristics = isSameInstance(finisher, Function.identity()) ?
IDENTITY_FINISH_CHARACTERISTICS :
NO_CHARACTERISTICS;
return Collector.of(LinkedMultiValueMap::new,
(map, annotation) -> annotation.asMap(options).forEach(map::add),
MergedAnnotationCollectors::merge, finisher, characteristics);
}
private static boolean isSameInstance(Object instance, Object candidate) {
return instance == candidate;
}
private static <E, L extends List<E>> L addAll(L list, L additions) {
list.addAll(additions);
return list;
}
private static <K, V> MultiValueMap<K, V> merge(MultiValueMap<K, V> map,
MultiValueMap<K, V> additions) {
map.addAll(additions);
return map;
}
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Predicate implementations that provide various test operations for
* {@link MergedAnnotation MergedAnnotations}.
*
* @author Phillip Webb
* @since 5.2
*/
public abstract class MergedAnnotationPredicates {
private MergedAnnotationPredicates() {
}
/**
* Returns a new {@link Predicate} that evaluates {@code true} if the
* {@link MergedAnnotation#getType() merged annotation type} is contained in
* the specified array.
* @param <A> the annotation type
* @param typeNames the names that should be matched
* @return a {@link Predicate} to test the annotation type
*/
public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(
String... typeNames) {
Assert.notNull(typeNames, "TypeNames must not be null");
return annotation -> ObjectUtils.containsElement(typeNames, annotation.getType());
}
/**
* Returns a new {@link Predicate} that evaluates {@code true} if the
* {@link MergedAnnotation#getType() merged annotation type} is contained in
* the specified array.
* @param <A> the annotation type
* @param types the types that should be matched
* @return a {@link Predicate} to test the annotation type
*/
public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(
Class<?>... types) {
Assert.notNull(types, "Types must not be null");
return annotation -> Arrays.stream(types)
.anyMatch(type -> type.getName().equals(annotation.getType()));
}
/**
* Returns a new {@link Predicate} that evaluates {@code true} if the
* {@link MergedAnnotation#getType() merged annotation type} is contained in
* the collection.
* @param <A> the annotation type
* @param types the type names or classes that should be matched
* @return a {@link Predicate} to test the annotation type
*/
public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(
Collection<?> types) {
Assert.notNull(types, "Types must not be null");
return annotation -> types.stream()
.map(type -> type instanceof Class ? ((Class<?>) type).getName() : type.toString())
.anyMatch(typeName -> typeName.equals(annotation.getType()));
}
/**
* Returns a new stateful, single use {@link Predicate} that matches only
* the first run of an extracted value. For example,
* {@code MergedAnnotationPredicates.firstRunOf(MergedAnnotation::depth)}
* will return the first annotation and a subsequent run of the same depth.
* NOTE: this predicate only matches the first first run, once the extracted
* value changes the predicate always returns {@code false}.
* @param valueExtractor function used to extract the value to check
* @return a {@link Predicate} that matches the first run of the extracted
* values
*/
public static <A extends Annotation> Predicate<MergedAnnotation<A>> firstRunOf(
Function<? super MergedAnnotation<A>, ?> valueExtractor) {
Assert.notNull(valueExtractor, "ValueExtractor must not be null");
return new FirstRunOfPredicate<>(valueExtractor);
}
/**
* Returns a new stateful, single use {@link Predicate} that matches
* annotations that are unique based on extracted key. For example
* {@code MergedAnnotationPredicates.unique(MergedAnnotation::type)} will
* match the first time a unique type is seen.
* @param keyExtractor function used to extract the key used to test for
* uniqueness
* @return a {@link Predicate} that matches unique annotation based on the
* extracted key
*/
public static <A extends Annotation, K> Predicate<MergedAnnotation<A>> unique(
Function<? super MergedAnnotation<A>, K> keyExtractor) {
Assert.notNull(keyExtractor, "KeyExtractor must not be null");
return new UniquePredicate<>(keyExtractor);
}
/**
* {@link Predicate} implementation used for
* {@link MergedAnnotationPredicates#firstRunOf(Function)}.
*/
private static class FirstRunOfPredicate<A extends Annotation>
implements Predicate<MergedAnnotation<A>> {
private final Function<? super MergedAnnotation<A>, ?> valueExtractor;
private boolean hasLastValue;
@Nullable
private Object lastValue;
FirstRunOfPredicate(
Function<? super MergedAnnotation<A>, ?> valueExtractor) {
this.valueExtractor = valueExtractor;
}
@Override
public boolean test(@Nullable MergedAnnotation<A> annotation) {
if (!this.hasLastValue) {
this.hasLastValue = true;
this.lastValue = this.valueExtractor.apply(annotation);
}
Object value = this.valueExtractor.apply(annotation);
return ObjectUtils.nullSafeEquals(value, this.lastValue);
}
}
/**
* {@link Predicate} implementation used for
* {@link MergedAnnotationPredicates#unique(Function)}.
*/
private static class UniquePredicate<A extends Annotation, K>
implements Predicate<MergedAnnotation<A>> {
private final Function<? super MergedAnnotation<A>, K> keyExtractor;
private final Set<K> seen = new HashSet<>();
UniquePredicate(Function<? super MergedAnnotation<A>, K> keyExtractor) {
this.keyExtractor = keyExtractor;
}
@Override
public boolean test(@Nullable MergedAnnotation<A> annotation) {
K key = this.keyExtractor.apply(annotation);
return this.seen.add(key);
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
/**
* Strategy interface used to select between two {@link MergedAnnotation}
* instances.
*
* @author Phillip Webb
* @since 5.2
* @param <A> the annotation type
* @see MergedAnnotationSelectors
*/
@FunctionalInterface
public interface MergedAnnotationSelector<A extends Annotation> {
/**
* Return {@code true} if the existing annotation is known to be the best
* candidate and any subsequent selections may be skipped.
* @param annotation the annotation to check
* @return {@code true} if the annotation is known to be the best candidate
*/
default boolean isBestCandidate(MergedAnnotation<A> annotation) {
return false;
}
/**
* Select the annotation that should be used.
* @param existing an existing annotation returned from an earlier result
* @param candidate a candidate annotation that may be better suited
* @return the most appropriate annotation from the {@code existing} or
* {@code candidate}
*/
MergedAnnotation<A> select(MergedAnnotation<A> existing,
MergedAnnotation<A> candidate);
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.util.function.Predicate;
/**
* {@link MergedAnnotationSelector} implementations that provide various options
* for {@link MergedAnnotation MergedAnnotations}.
*
* @author Phillip Webb
* @since 5.2
* @see MergedAnnotations#get(Class, Predicate, MergedAnnotationSelector)
* @see MergedAnnotations#get(String, Predicate, MergedAnnotationSelector)
*/
public abstract class MergedAnnotationSelectors {
private static final MergedAnnotationSelector<?> NEAREST = new Nearest();
private static final MergedAnnotationSelector<?> FIRST_DIRECTLY_DECLARED = new FirstDirectlyDeclared();
private MergedAnnotationSelectors() {
}
/**
* Select the nearest annotation, i.e. the one with the lowest depth.
* @return a selector that picks the annotation with the lowest depth
*/
@SuppressWarnings("unchecked")
public static <A extends Annotation> MergedAnnotationSelector<A> nearest() {
return (MergedAnnotationSelector<A>) NEAREST;
}
/**
* Select the first directly declared annotation when possible. If not direct
* annotations are declared then the earliest annotation is selected.
* @return a selector that picks the first directly declared annotation whenever possible
*/
@SuppressWarnings("unchecked")
public static <A extends Annotation> MergedAnnotationSelector<A> firstDirectlyDeclared() {
return (MergedAnnotationSelector<A>) FIRST_DIRECTLY_DECLARED;
}
/**
* {@link MergedAnnotationSelector} to select the nearest annotation.
*/
private static class Nearest implements MergedAnnotationSelector<Annotation> {
@Override
public boolean isBestCandidate(MergedAnnotation<Annotation> annotation) {
return annotation.getDepth() == 0;
}
@Override
public MergedAnnotation<Annotation> select(MergedAnnotation<Annotation> existing,
MergedAnnotation<Annotation> candidate) {
if (candidate.getDepth() < existing.getDepth()) {
return candidate;
}
return existing;
}
}
/**
* {@link MergedAnnotationSelector} to select the first directly declared
* annotation.
*/
private static class FirstDirectlyDeclared implements MergedAnnotationSelector<Annotation> {
@Override
public boolean isBestCandidate(MergedAnnotation<Annotation> annotation) {
return annotation.getDepth() == 0;
}
@Override
public MergedAnnotation<Annotation> select(MergedAnnotation<Annotation> existing,
MergedAnnotation<Annotation> candidate) {
if (existing.getDepth() > 0 && candidate.getDepth() == 0) {
return candidate;
}
return existing;
}
}
}

View File

@@ -0,0 +1,412 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.springframework.lang.Nullable;
/**
* Provides access to a collection of merged annotations, usually obtained from
* a from a source such as a {@link Class} or {@link Method}. Each merged
* annotation represent a view where the attribute values may be "merged" from
* different source values, typically:
*
* <ul>
* <li>Explicit and Implicit {@link AliasFor @AliasFor} declarations on one or
* attributes within the annotation.</li>
* <li>Explicit {@link AliasFor @AliasFor} declarations for a
* meta-annotation.</li>
* <li>Convention based attribute aliases for a meta-annotation</li>
* <li>From a meta-annotation declaration.</li>
* </ul>
*
* <p>For example, a {@code @PostMapping} annotation might be defined as follows:
*
* <pre class="code">
* &#064;Retention(RetentionPolicy.RUNTIME)
* &#064;RequestMapping(method = RequestMethod.POST)
* public &#064;interface PostMapping {
*
* &#064;AliasFor(attribute = "path")
* String[] value() default {};
*
* &#064;AliasFor(attribute = "value")
* String[] path() default {};
*
* }
* </pre>
*
* If a method is annotated with {@code @PostMapping("/home")} it will contain
* merged annotations for both {@code @PostMapping} and the meta-annotation
* {@code @RequestMapping}. The merged view of the {@code @RequestMapping}
* annotation will contain the following attributes:
*
* <p><table>
* <tr>
* <th>Name</th>
* <th>Value</th>
* <th>Source</th>
* </tr>
* <tr>
* <td>value</td>
* <td>"/home"</td>
* <td>Declared {@code @PostMapping}</td>
* </tr>
* <tr>
* <td>path</td>
* <td>"/home"</td>
* <td>Explicit {@code @AliasFor}</td>
* </tr>
* <tr>
* <td>method</td>
* <td>RequestMethod.POST</td>
* <td>Declared meta-annotation</td>
* </tr>
* </table>
*
* <p>{@link MergedAnnotations} can be obtained {@link #from(AnnotatedElement)
* from} any Java {@link AnnotatedElement}. They may also used for sources that
* don't use reflection (such as those that directly parse bytecode).
*
* <p>Different {@link SearchStrategy search strategies} can be used to locate
* related source elements that contain the annotations to be aggregated
* together. For example, {@link SearchStrategy#EXHAUSTIVE} will search both
* superclasses and implemented interfaces.
*
* <p>From a {@link MergedAnnotations} instance you can either {@link #get(String)}
* a single annotation, or {@link #stream() stream all annotations} or just
* those that match {@link #stream(String) a specific type}. You can also
* quickly tell if an annotation {@link #isPresent(String) is present}.
*
* <p>Here are some typical examples:
*
* <pre class="code">
* // is an annotation present or meta-present
* mergedAnnotations.isPresent(ExampleAnnotation.class);
*
* // get the merged "value" attribute of ExampleAnnotation (either direct or
* // meta-present)
* mergedAnnotations.get(ExampleAnnotation.class).getString("value");
*
* // get all meta-annotations but no direct annotations
* mergedAnnotations.stream().anyMatch(MergedAnnotation::isMetaPresent);
*
* // get all ExampleAnnotation declarations (include any meta-annotations) and
* // print the merged "value" attributes
* mergedAnnotations.stream(ExampleAnnotation.class).map(
* a -> a.getString("value")).forEach(System.out::println);
* </pre>
*
* @author Phillip Webb
* @since 5.2
* @see MergedAnnotation
* @see MergedAnnotationCollectors
* @see MergedAnnotationPredicates
* @see MergedAnnotationSelectors
*/
public interface MergedAnnotations extends Iterable<MergedAnnotation<Annotation>> {
/**
* Return if the specified annotation is either directly present, or
* meta-present. Equivalent to calling
* {@code get(annotationType).isPresent()}.
* @param annotationType the annotation type to check
* @return {@code true} if the annotation is present
*/
<A extends Annotation> boolean isPresent(@Nullable Class<A> annotationType);
/**
* Return if the specified annotation is directly present. Equivalent to
* calling {@code get(annotationType).isDirectlyPresent()}.
* @param annotationType the annotation type to check
* @return {@code true} if the annotation is present
*/
boolean isPresent(@Nullable String annotationType);
/**
* Return if the specified annotation is directly present. Equivalent to
* calling {@code get(annotationType).isDirectlyPresent()}.
* @param annotationType the annotation type to check
* @return {@code true} if the annotation is present
*/
<A extends Annotation> boolean isDirectlyPresent(@Nullable Class<A> annotationType);
/**
* Return if the specified annotation is either directly present, or
* meta-present. Equivalent to calling
* {@code get(annotationType).isPresent()}.
* @param annotationType the annotation type to check
* @return {@code true} if the annotation is present
*/
boolean isDirectlyPresent(@Nullable String annotationType);
/**
* Return the {@link MergedAnnotationSelectors#nearest() nearest} matching
* annotation or meta-annotation of the specified type, or
* {@link MergedAnnotation#missing()} if none is present.
* @param annotationType the annotation type to get
* @return a {@link MergedAnnotation} instance
*/
<A extends Annotation> MergedAnnotation<A> get(@Nullable Class<A> annotationType);
/**
* Return the {@link MergedAnnotationSelectors#nearest() nearest} matching
* annotation or meta-annotation of the specified type, or
* {@link MergedAnnotation#missing()} if none is present.
* @param annotationType the annotation type to get
* @param predicate a predicate that must match, or {@code null} if only
* type matching is required
* @return a {@link MergedAnnotation} instance
* @see MergedAnnotationPredicates
*/
<A extends Annotation> MergedAnnotation<A> get(@Nullable Class<A> annotationType,
@Nullable Predicate<? super MergedAnnotation<A>> predicate);
/**
* Return a matching annotation or meta-annotation of the specified type, or
* {@link MergedAnnotation#missing()} if none is present.
* @param annotationType the annotation type to get
* @param predicate a predicate that must match, or {@code null} if only
* type matching is required
* @param selector a selector used to choose the most appropriate annotation
* within an aggregate, or {@code null} to select the
* {@link MergedAnnotationSelectors#nearest() nearest}.
* @return a {@link MergedAnnotation} instance
* @see MergedAnnotationPredicates
* @see MergedAnnotationSelectors
*/
<A extends Annotation> MergedAnnotation<A> get(@Nullable Class<A> annotationType,
@Nullable Predicate<? super MergedAnnotation<A>> predicate,
@Nullable MergedAnnotationSelector<A> selector);
/**
* Return the {@link MergedAnnotationSelectors#nearest() nearest} matching
* annotation or meta-annotation of the specified type, or
* {@link MergedAnnotation#missing()} if none is present.
* @param annotationType the annotation type to get
* @return a {@link MergedAnnotation} instance
*/
<A extends Annotation> MergedAnnotation<A> get(@Nullable String annotationType);
/**
* Return the {@link MergedAnnotationSelectors#nearest() nearest} matching
* annotation or meta-annotation of the specified type, or
* {@link MergedAnnotation#missing()} if none is present.
* @param annotationType the annotation type to get
* @param predicate a predicate that must match, or {@code null} if only
* type matching is required
* @return a {@link MergedAnnotation} instance
* @see MergedAnnotationPredicates
*/
<A extends Annotation> MergedAnnotation<A> get(@Nullable String annotationType,
@Nullable Predicate<? super MergedAnnotation<A>> predicate);
/**
* Return a matching annotation or meta-annotation of the specified type, or
* {@link MergedAnnotation#missing()} if none is present.
* @param annotationType the annotation type to get
* @param predicate a predicate that must match, or {@code null} if only
* type matching is required
* @param selector a selector used to choose the most appropriate annotation
* within an aggregate, or {@code null} to select the
* {@link MergedAnnotationSelectors#nearest() nearest}.
* @return a {@link MergedAnnotation} instance
* @see MergedAnnotationPredicates
* @see MergedAnnotationSelectors
*/
<A extends Annotation> MergedAnnotation<A> get(@Nullable String annotationType,
@Nullable Predicate<? super MergedAnnotation<A>> predicate,
@Nullable MergedAnnotationSelector<A> selector);
/**
* Stream all annotations and meta-annotations that match the specified
* type. The resulting stream follows the same ordering rules are
* {@link #stream()}.
* @param annotationType the annotation type to match
* @return a stream of matching annotations
*/
<A extends Annotation> Stream<MergedAnnotation<A>> stream(
@Nullable Class<A> annotationType);
/**
* Stream all annotations and meta-annotations that match the specified
* type.The resulting stream follows the same ordering rules are
* {@link #stream()}.
* @param annotationType the annotation type to match
* @return a stream of matching annotations
*/
<A extends Annotation> Stream<MergedAnnotation<A>> stream(
@Nullable String annotationType);
/**
* Stream all contained annotations and meta-annotations contained in this
* collection. The resulting stream is ordered first by the
* {@link MergedAnnotation#getAggregateIndex() aggregate index}, and then by
* the annotation depth (with the closest annotations first). This ordering
* means that, for most use-cases, the most suitable annotations appear
* earliest in the stream.
* @return a stream of annotations
*/
Stream<MergedAnnotation<Annotation>> stream();
/**
* Create a new {@link MergedAnnotations} instance containing all
* annotations and meta-annotations from the specified element. The
* resulting instance will not include any inherited annotations, if you
* want to include those as well you should use
* {@link #from(AnnotatedElement, SearchStrategy)} with an appropriate
* {@link SearchStrategy}.
* @param element the source element
* @return a {@link MergedAnnotations} instance containing the element
* annotations
*/
static MergedAnnotations from(@Nullable AnnotatedElement element) {
return from(element, SearchStrategy.DIRECT);
}
/**
* Create a new {@link MergedAnnotations} instance containing all
* annotations and meta-annotations from the specified element and,
* depending on the {@link SearchStrategy}, related inherited elements.
* @param element the source element
* @param searchStrategy the search strategy to use
* @return a {@link MergedAnnotations} instance containing the merged
* element annotations
*/
static MergedAnnotations from(@Nullable AnnotatedElement element,
SearchStrategy searchStrategy) {
return from(element, searchStrategy, RepeatableContainers.standardRepeatables(),
AnnotationFilter.PLAIN);
}
/**
* Create a new {@link MergedAnnotations} instance containing all
* annotations and meta-annotations from the specified element and,
* depending on the {@link SearchStrategy}, related inherited elements.
* @param element the source element
* @param searchStrategy the search strategy to use
* @param repeatableContainers the repeatable containers that may be used by
* the element annotations or the meta-annotations
* @param annotationFilter an annotation filter used to restrict the
* annotations considered
* @return a {@link MergedAnnotations} instance containing the merged
* element annotations
*/
static MergedAnnotations from(@Nullable AnnotatedElement element,
SearchStrategy searchStrategy, RepeatableContainers repeatableContainers,
AnnotationFilter annotationFilter) {
return TypeMappedAnnotations.from(element, searchStrategy, repeatableContainers,
annotationFilter);
}
/**
* Create a new {@link MergedAnnotations} instance from the specified
* annotations.
* @param annotations the annotations to include
* @return a {@link MergedAnnotations} instance containing the annotations
* @see #from(Object, Annotation...)
*/
static MergedAnnotations from(Annotation... annotations) {
return from(null, annotations);
}
/**
* Create a new {@link MergedAnnotations} instance from the specified
* annotations.
* @param source the source for the annotations. This source is used only
* for information and logging. It does not need to <em>actually</em>
* contain the specified annotations and it will not be searched.
* @param annotations the annotations to include
* @return a {@link MergedAnnotations} instance containing the annotations
* @see #from(Annotation...)
* @see #from(AnnotatedElement)
*/
static MergedAnnotations from(@Nullable Object source, Annotation... annotations) {
return from(source, annotations, RepeatableContainers.standardRepeatables(),
AnnotationFilter.PLAIN);
}
/**
* Create a new {@link MergedAnnotations} instance from the specified
* annotations.
* @param source the source for the annotations. This source is used only
* for information and logging. It does not need to <em>actually</em>
* contain the specified annotations and it will not be searched.
* @param annotations the annotations to include
* @param repeatableContainers the repeatable containers that may be used by
* meta-annotations
* @param annotationFilter an annotation filter used to restrict the
* annotations considered
* @return a {@link MergedAnnotations} instance containing the annotations
*/
static MergedAnnotations from(@Nullable Object source, Annotation[] annotations,
RepeatableContainers repeatableContainers,
AnnotationFilter annotationFilter) {
return TypeMappedAnnotations.from(source, annotations, repeatableContainers,
annotationFilter);
}
/**
* Search strategies supported by
* {@link MergedAnnotations#from(AnnotatedElement, SearchStrategy)}. Each
* strategy creates a different set of aggregates that will be combined to
* create the final {@link MergedAnnotations}.
*/
enum SearchStrategy {
/**
* Find only directly declared annotations, without considering
* {@link Inherited @Inherited} annotations and without searching
* super-classes or implemented interfaces.
*/
DIRECT,
/**
* Find all directly declared annotations as well any
* {@link Inherited @Inherited} super-class annotations. This strategy
* is only really useful when used with {@link Class} types since the
* {@link Inherited @Inherited} annotation is ignored for all other
* {@link AnnotatedElement annotated elements}. This strategy does not
* search implemented interfaces.
*/
INHERITED_ANNOTATIONS,
/**
* Find all directly declared and super-class annotations. This strategy
* is similar to {@link #INHERITED_ANNOTATIONS} except the annotations
* do not need to be meta-annotated with {@link Inherited @Inherited}.
* This strategy does not search implemented interfaces.
*/
SUPER_CLASS,
/**
* Perform a full search of all related elements, include those on any
* super-classes or implemented interfaces. Superclass annotations do
* not need to be meta-annotated with {@link Inherited @Inherited}.
*/
EXHAUSTIVE
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import org.springframework.lang.Nullable;
/**
* A {@link AbstractMergedAnnotation} used as the implementation of
* {@link MergedAnnotation#missing()}.
*
* @author Phillip Webb
* @since 5.2
* @param <A> the annotation type
*/
final class MissingMergedAnnotation<A extends Annotation>
extends AbstractMergedAnnotation<A> {
private static final MissingMergedAnnotation<?> INSTANCE = new MissingMergedAnnotation<>();
private MissingMergedAnnotation() {
}
@Override
public String getType() {
throw new NoSuchElementException("Unable to get type for missing annotation");
}
@Override
public boolean isPresent() {
return false;
}
@Override
@Nullable
public Object getSource() {
return null;
}
@Override
@Nullable
public MergedAnnotation<?> getParent() {
return null;
}
@Override
public int getDepth() {
return -1;
}
@Override
public int getAggregateIndex() {
return -1;
}
public boolean hasNonDefaultValue(String attributeName) {
throw new NoSuchElementException(
"Unable to check non-default value for missing annotation");
}
@Override
public boolean hasDefaultValue(String attributeName) {
throw new NoSuchElementException(
"Unable to check default value for missing annotation");
}
@Override
public <T> Optional<T> getValue(String attributeName, Class<T> type) {
return Optional.empty();
}
@Override
public <T> Optional<T> getDefaultValue(@Nullable String attributeName, Class<T> type) {
return Optional.empty();
}
@Override
public MergedAnnotation<A> filterAttributes(Predicate<String> predicate) {
return this;
}
@Override
public MergedAnnotation<A> withNonMergedAttributes() {
return this;
}
@Override
public Map<String, Object> asMap(MapValues... options) {
return Collections.emptyMap();
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T extends Map<String, Object>> T asMap(
@Nullable Function<MergedAnnotation<?>, T> factory, MapValues... options) {
if (factory != null) {
return factory.apply(this);
}
return (T) ((Map) Collections.emptyMap());
}
@Override
public String toString() {
return "(missing)";
}
@Override
public <T extends Annotation> MergedAnnotation<T> getAnnotation(String attributeName,
Class<T> type) throws NoSuchElementException {
throw new NoSuchElementException(
"Unable to get attribute value for missing annotation");
}
@Override
public <T extends Annotation> MergedAnnotation<T>[] getAnnotationArray(
String attributeName, Class<T> type) throws NoSuchElementException {
throw new NoSuchElementException(
"Unable to get attribute value for missing annotation");
}
@Override
protected <T> T getAttributeValue(String attributeName, Class<T> type) {
throw new NoSuchElementException(
"Unable to get attribute value for missing annotation");
}
protected A createSynthesized() {
throw new NoSuchElementException("Unable to synthesize missing annotation");
}
@SuppressWarnings("unchecked")
static <A extends Annotation> MergedAnnotation<A> getInstance() {
return (MergedAnnotation<A>) INSTANCE;
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.util.Arrays;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link AnnotationFilter} implementation used for
* {@link AnnotationFilter#packages(String...)}.
*
* @author Phillip Webb
* @since 5.2
*/
class PackagesAnnotationFilter implements AnnotationFilter {
private final String[] prefixes;
private final int hashCode;
PackagesAnnotationFilter(String... packages) {
Assert.notNull(packages, "Packages must not be null");
this.prefixes = new String[packages.length];
for (int i = 0; i < packages.length; i++) {
Assert.hasText(packages[i], "Package must not have empty elements");
this.prefixes[i] = packages[i] + ".";
}
Arrays.sort(this.prefixes);
this.hashCode = Arrays.hashCode(this.prefixes);
}
@Override
public boolean matches(@Nullable String annotationType) {
if (annotationType != null) {
for (String prefix : this.prefixes) {
if (annotationType.startsWith(prefix)) {
return true;
}
}
}
return false;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
PackagesAnnotationFilter other = (PackagesAnnotationFilter) obj;
return Arrays.equals(this.prefixes, other.prefixes);
}
@Override
public int hashCode() {
return this.hashCode;
}
@Override
public String toString() {
return "Packages annotation filter: " +
StringUtils.arrayToCommaDelimitedString(this.prefixes);
}
}

View File

@@ -0,0 +1,290 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
* Strategy used to determine annotations that act as containers for other
* annotations. The {@link #standardRepeatables()} method provides a default
* strategy that respects Java's {@link Repeatable @Repeatable} support and
* should be suitable for most situations.
* <p> The {@link #of} method can be used to register relationships for
* annotations that do not wish to use {@link Repeatable @Repeatable}.
*
* <p>To completely disable repeatable support use {@link #none()}.
*
* @author Phillip Webb
* @since 5.2
*/
public abstract class RepeatableContainers {
@Nullable
private final RepeatableContainers parent;
private RepeatableContainers(@Nullable RepeatableContainers parent) {
this.parent = parent;
}
/**
* Add an additional explicit relationship between a contained and
* repeatable annotation.
* @param container the container type
* @param repeatable the contained repeatable type
* @return a new {@link RepeatableContainers} instance
*/
public RepeatableContainers and(Class<? extends Annotation> container,
Class<? extends Annotation> repeatable) {
return new ExplicitRepeatableContainer(this, repeatable, container);
}
@Nullable
Annotation[] findRepeatedAnnotations(Annotation annotation) {
if (this.parent == null) {
return null;
}
return this.parent.findRepeatedAnnotations(annotation);
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
RepeatableContainers other = (RepeatableContainers) obj;
return Objects.equals(this.parent, other.parent);
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.parent);
}
/**
* Return a {@link RepeatableContainers} instance that searches using Java's
* {@link Repeatable @Repeatable} annotation.
* @return a {@link RepeatableContainers} instance
*/
public static RepeatableContainers standardRepeatables() {
return StandardRepeatableContainers.INSTANCE;
}
/**
* Return a {@link RepeatableContainers} instance that uses a defined
* container and repeatable type.
* @param repeatable the contained repeatable annotation
* @param container the container annotation or {@code null}. If specified,
* this annotation must declare a {@code value} attribute returning an array
* of repeatable annotations. If not specified, the container will be
* deduced by inspecting the {@code @Repeatable} annotation on
* {@code repeatable}.
* @return a {@link RepeatableContainers} instance
*/
public static RepeatableContainers of(Class<? extends Annotation> repeatable,
@Nullable Class<? extends Annotation> container) {
return new ExplicitRepeatableContainer(null, repeatable, container);
}
/**
* Return a {@link RepeatableContainers} instance that does not expand any
* repeatable annotations.
* @return a {@link RepeatableContainers} instance
*/
public static RepeatableContainers none() {
return NoRepeatableContainers.INSTANCE;
}
/**
* Standard {@link RepeatableContainers} implementation that searches using
* Java's {@link Repeatable @Repeatable} annotation.
*/
private static class StandardRepeatableContainers extends RepeatableContainers {
private static final Map<Class<? extends Annotation>, Object> cache =
new ConcurrentReferenceHashMap<>();
private static final Object NONE = new Object();
private static StandardRepeatableContainers INSTANCE =
new StandardRepeatableContainers();
StandardRepeatableContainers() {
super(null);
}
@Override
@Nullable
Annotation[] findRepeatedAnnotations(Annotation annotation) {
Method method = getRepeatedAnnotationsMethod(annotation.annotationType());
if (method != null) {
return (Annotation[]) ReflectionUtils.invokeMethod(method, annotation);
}
return super.findRepeatedAnnotations(annotation);
}
@Nullable
private static Method getRepeatedAnnotationsMethod(
Class<? extends Annotation> annotationType) {
Object result = cache.computeIfAbsent(annotationType,
StandardRepeatableContainers::computeRepeatedAnnotationsMethod);
return result != NONE ? (Method) result : null;
}
private static Object computeRepeatedAnnotationsMethod(
Class<? extends Annotation> annotationType) {
AttributeMethods methods = AttributeMethods.forAnnotationType(annotationType);
if (methods.isOnlyValueAttribute()) {
Method method = methods.get("value");
if (method == null) {
return NONE;
}
Class<?> returnType = method.getReturnType();
if (returnType.isArray()) {
Class<?> componentType = returnType.getComponentType();
if (Annotation.class.isAssignableFrom(componentType)
&& componentType.isAnnotationPresent(Repeatable.class)) {
return method;
}
}
}
return NONE;
}
}
/**
* A single explicit mapping.
*/
private static class ExplicitRepeatableContainer extends RepeatableContainers {
private final Class<? extends Annotation> repeatable;
private final Class<? extends Annotation> container;
private final Method valueMethod;
ExplicitRepeatableContainer(@Nullable RepeatableContainers parent,
Class<? extends Annotation> repeatable,
@Nullable Class<? extends Annotation> container) {
super(parent);
Assert.notNull(repeatable, "Repeatable must not be null");
if (container == null) {
container = deduceContainer(repeatable);
}
Method valueMethod = AttributeMethods.forAnnotationType(container).get("value");
try {
if (valueMethod == null) {
throw new NoSuchMethodException("No value method found");
}
Class<?> returnType = valueMethod.getReturnType();
if (!returnType.isArray() || returnType.getComponentType() != repeatable) {
throw new AnnotationConfigurationException("Container type [" +
container.getName() +
"] must declare a 'value' attribute for an array of type [" +
repeatable.getName() + "]");
}
}
catch (AnnotationConfigurationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new AnnotationConfigurationException(
"Invalid declaration of container type [" + container.getName() +
"] for repeatable annotation [" + repeatable.getName() + "]",
ex);
}
this.repeatable = repeatable;
this.container = container;
this.valueMethod = valueMethod;
}
private Class<? extends Annotation> deduceContainer(
Class<? extends Annotation> repeatable) {
Repeatable annotation = repeatable.getAnnotation(Repeatable.class);
Assert.notNull(annotation, "Annotation type must be a repeatable annotation: " +
"failed to resolve container type for " + repeatable.getName());
return annotation.value();
}
@Override
@Nullable
Annotation[] findRepeatedAnnotations(Annotation annotation) {
if (this.container.isAssignableFrom(annotation.annotationType())) {
return (Annotation[]) ReflectionUtils.invokeMethod(this.valueMethod, annotation);
}
return super.findRepeatedAnnotations(annotation);
}
@Override
public boolean equals(@Nullable Object obj) {
if (super.equals(obj)) {
ExplicitRepeatableContainer other = (ExplicitRepeatableContainer) obj;
return this.container.equals(other.container) &&
this.repeatable.equals(other.repeatable);
}
return false;
}
@Override
public int hashCode() {
int hashCode = super.hashCode();
hashCode = 31 * hashCode + this.container.hashCode();
hashCode = 31 * hashCode + this.repeatable.hashCode();
return hashCode;
}
}
/**
* No repeatable containers.
*/
private static class NoRepeatableContainers extends RepeatableContainers {
private static NoRepeatableContainers INSTANCE = new NoRepeatableContainers();
NoRepeatableContainers() {
super(null);
}
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.Objects;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
* {@link InvocationHandler} for an {@link Annotation} that Spring has
* <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional
* functionality.
*
* @author Sam Brannen
* @author Phillip Webb
* @since 5.2
* @param <A> the annotation type
* @see Annotation
* @see AnnotationAttributeExtractor
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
*/
class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation>
implements InvocationHandler {
private final MergedAnnotation<?> annotation;
private final Class<A> type;
private final AttributeMethods attributes;
@Nullable
private volatile Integer hashCode;
private SynthesizedMergedAnnotationInvocationHandler(MergedAnnotation<A> annotation,
Class<A> type) {
Assert.notNull(annotation, "Annotation must not be null");
Assert.notNull(type, "Type must not be null");
Assert.isTrue(type.isAnnotation(), "Type must be an annotation");
this.annotation = annotation;
this.type = type;
this.attributes = AttributeMethods.forAnnotationType(type);
for (int i = 0; i < this.attributes.size(); i++) {
getAttributeValue(this.attributes.get(i));
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
if (ReflectionUtils.isEqualsMethod(method)) {
return annotationEquals(args[0]);
}
if (ReflectionUtils.isHashCodeMethod(method)) {
return annotationHashCode();
}
if (ReflectionUtils.isToStringMethod(method)) {
return this.annotation.toString();
}
if (isAnnotationTypeMethod(method)) {
return this.type;
}
if (this.attributes.indexOf(method.getName()) != -1) {
return getAttributeValue(method);
}
throw new AnnotationConfigurationException(String.format(
"Method [%s] is unsupported for synthesized annotation type [%s]", method,
this.type));
}
private boolean isAnnotationTypeMethod(Method method) {
return Objects.equals(method.getName(), "annotationType")
&& method.getParameterCount() == 0;
}
/**
* See {@link Annotation#equals(Object)} for a definition of the required
* algorithm.
* @param other the other object to compare against
*/
private boolean annotationEquals(Object other) {
if (this == other) {
return true;
}
if (!this.type.isInstance(other)) {
return false;
}
for (int i = 0; i < this.attributes.size(); i++) {
Method attribute = this.attributes.get(i);
Object thisValue = getAttributeValue(attribute);
Object otherValue = ReflectionUtils.invokeMethod(attribute, other);
if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
return false;
}
}
return true;
}
/**
* See {@link Annotation#hashCode()} for a definition of the required
* algorithm.
*/
private int annotationHashCode() {
Integer hashCode = this.hashCode;
if (hashCode == null) {
hashCode = computeHashCode();
this.hashCode = hashCode;
}
return hashCode;
}
private Integer computeHashCode() {
int hashCode = 0;
for (int i = 0; i < this.attributes.size(); i++) {
Method attribute = this.attributes.get(i);
Object value = getAttributeValue(attribute);
hashCode += (127 * attribute.getName().hashCode()) ^ getValueHashCode(value);
}
return hashCode;
}
private int getValueHashCode(Object value) {
// Use Arrays.hashCode since ObjectUtils doesn't comply to to
// Annotation#hashCode()
if (value instanceof boolean[]) {
return Arrays.hashCode((boolean[]) value);
}
if (value instanceof byte[]) {
return Arrays.hashCode((byte[]) value);
}
if (value instanceof char[]) {
return Arrays.hashCode((char[]) value);
}
if (value instanceof double[]) {
return Arrays.hashCode((double[]) value);
}
if (value instanceof float[]) {
return Arrays.hashCode((float[]) value);
}
if (value instanceof int[]) {
return Arrays.hashCode((int[]) value);
}
if (value instanceof long[]) {
return Arrays.hashCode((long[]) value);
}
if (value instanceof short[]) {
return Arrays.hashCode((short[]) value);
}
if (value instanceof Object[]) {
return Arrays.hashCode((Object[]) value);
}
return value.hashCode();
}
private Object getAttributeValue(Method method) {
String name = method.getName();
Class<?> type = ClassUtils.resolvePrimitiveIfNecessary(method.getReturnType());
return this.annotation.getValue(name, type).orElseThrow(
() -> new NoSuchElementException("No value found for attribute named '"
+ name + "' in merged annotation " + this.annotation.getType()));
}
@SuppressWarnings("unchecked")
static <A extends Annotation> A createProxy(MergedAnnotation<A> annotation,
Class<A> type) {
ClassLoader classLoader = type.getClassLoader();
InvocationHandler handler = new SynthesizedMergedAnnotationInvocationHandler<>(
annotation, type);
Class<?>[] interfaces = isVisible(classLoader, SynthesizedAnnotation.class)
? new Class<?>[] { type, SynthesizedAnnotation.class }
: new Class<?>[] { type };
return (A) Proxy.newProxyInstance(classLoader, interfaces, handler);
}
private static boolean isVisible(ClassLoader classLoader, Class<?> interfaceClass) {
try {
return Class.forName(interfaceClass.getName(), false,
classLoader) == interfaceClass;
}
catch (ClassNotFoundException ex) {
return false;
}
}
}

View File

@@ -0,0 +1,614 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* {@link MergedAnnotation} that adapts attributes from a root annotation by
* applying the mapping and mirroring rules of an {@link AnnotationTypeMapping}.
*
* <p>Root attribute values are extracted from a source object using a supplied
* {@code BiFunction}. This allows various different annotation models to be
* supported by the same class. For example, the attributes source might be an
* actual {@link Annotation} instance where methods on the annotation instance
* are {@link ReflectionUtils#invokeMethod(Method, Object) invoked} to extract
* values. Equally, the source could be a simple {@link Map} with values
* extracted using {@link Map#get(Object)}.
*
* <p>Extracted root attribute values must be compatible with the attribute
* return type, namely:
*
* <p><table border="1">
* <tr><th >Return Type</th><th >Extracted Type</th></tr>
* <tr><td>Class</td><td>Class or String</td></tr>
* <tr><td>Class[]</td><td>Class[] or String[]</td></tr>
* <tr><td>Annotation</td><td>Annotation, Map or Object compatible with the value
* extractor</td></tr>
* <tr><td>Annotation[]</td><td>Annotation[], Map[] or Object[] where elements are
* compatible with the value extractor</td></tr>
* <tr><td>Other types</td><td>An exact match or the appropriate primitive wrapper</td></tr>
* </table>
*
* @author Phillip Webb
* @since 5.2
* @param <A> the annotation type
* @see TypeMappedAnnotations
*/
final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnnotation<A> {
private final AnnotationTypeMapping mapping;
@Nullable
private final Object source;
@Nullable
private final Object rootAttributes;
private final BiFunction<Method, Object, Object> valueExtractor;
private final int aggregateIndex;
private final boolean useMergedValues;
@Nullable
private final Predicate<String> attributeFilter;
private final int[] resolvedRootMirrors;
private final int[] resolvedMirrors;
@Nullable
private String string;
private TypeMappedAnnotation(AnnotationTypeMapping mapping,
@Nullable Object source, @Nullable Object rootAttributes,
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex) {
this(mapping, source, rootAttributes, valueExtractor, aggregateIndex, null);
}
private TypeMappedAnnotation(AnnotationTypeMapping mapping,
@Nullable Object source, @Nullable Object rootAttributes,
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex,
@Nullable int[] resolvedRootMirrors) {
this.source = source;
this.rootAttributes = rootAttributes;
this.valueExtractor = valueExtractor;
this.mapping = mapping;
this.aggregateIndex = aggregateIndex;
this.useMergedValues = true;
this.attributeFilter = null;
this.resolvedRootMirrors = resolvedRootMirrors != null ? resolvedRootMirrors
: mapping.getRoot().getMirrorSets().resolve(source, rootAttributes,
this.valueExtractor);
this.resolvedMirrors = getDepth() == 0 ? this.resolvedRootMirrors
: mapping.getMirrorSets().resolve(source, this,
this::getValueForMirrorResolution);
}
private TypeMappedAnnotation(AnnotationTypeMapping mapping,
@Nullable Object source, @Nullable Object rootAnnotation,
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex,
boolean useMergedValues, @Nullable Predicate<String> attributeFilter,
int[] resolvedRootMirrors, int[] resolvedMirrors) {
this.source = source;
this.rootAttributes = rootAnnotation;
this.valueExtractor = valueExtractor;
this.mapping = mapping;
this.aggregateIndex = aggregateIndex;
this.useMergedValues = useMergedValues;
this.attributeFilter = attributeFilter;
this.resolvedRootMirrors = resolvedRootMirrors;
this.resolvedMirrors = resolvedMirrors;
}
@Nullable
private Object getValueForMirrorResolution(Method attribute, Object annotation) {
int attributeIndex = this.mapping.getAttributes().indexOf(attribute);
boolean valueAttribute = VALUE.equals(attribute.getName());
return getValue(attributeIndex, !valueAttribute, false);
}
@Override
public String getType() {
return getAnnotationType().getName();
}
@Override
public boolean isPresent() {
return true;
}
@Override
public int getDepth() {
return this.mapping.getDepth();
}
@Override
public int getAggregateIndex() {
return this.aggregateIndex;
}
@Override
@Nullable
public Object getSource() {
return this.source;
}
@Override
@Nullable
public MergedAnnotation<?> getParent() {
AnnotationTypeMapping parentMapping = this.mapping.getParent();
if (parentMapping == null) {
return null;
}
return new TypeMappedAnnotation<>(parentMapping, this.source, this.rootAttributes,
this.valueExtractor, this.aggregateIndex, this.resolvedRootMirrors);
}
@Override
public boolean hasDefaultValue(String attributeName) {
int attributeIndex = getAttributeIndex(attributeName, true);
Object value = getValue(attributeIndex, true, true);
return value == null || this.mapping.isEquivalentToDefaultValue(attributeIndex, value,
this.valueExtractor);
}
@Override
@SuppressWarnings("unchecked")
public <T extends Annotation> MergedAnnotation<T> getAnnotation(String attributeName,
Class<T> type) throws NoSuchElementException {
Assert.notNull(attributeName, "AttributeName must not be null");
Assert.notNull(type, "Type must not be null");
int attributeIndex = getAttributeIndex(attributeName, true);
Method attribute = this.mapping.getAttributes().get(attributeIndex);
Assert.isAssignable(type, attribute.getReturnType(),
"Attribute " + attributeName + " type mismatch:");
return (MergedAnnotation<T>) getRequiredValue(attributeIndex, Object.class);
}
@Override
@SuppressWarnings("unchecked")
public <T extends Annotation> MergedAnnotation<T>[] getAnnotationArray(
String attributeName, Class<T> type) throws NoSuchElementException {
Assert.notNull(attributeName, "AttributeName must not be null");
Assert.notNull(type, "Type must not be null");
int attributeIndex = getAttributeIndex(attributeName, true);
Method attribute = this.mapping.getAttributes().get(attributeIndex);
Class<?> componentType = attribute.getReturnType().getComponentType();
Assert.notNull(componentType, () -> "Attribute " + attributeName + " is not an array");
Assert.isAssignable(type, componentType, "Attribute " + attributeName + " component type mismatch:");
return (MergedAnnotation<T>[]) getRequiredValue(attributeIndex, Object.class);
}
@Override
public <T> Optional<T> getDefaultValue(String attributeName, Class<T> type) {
int attributeIndex = getAttributeIndex(attributeName, false);
if (attributeIndex == -1) {
return Optional.empty();
}
Method attribute = this.mapping.getAttributes().get(attributeIndex);
return Optional.ofNullable(adapt(attribute, attribute.getDefaultValue(), type));
}
@Override
public MergedAnnotation<A> filterAttributes(Predicate<String> predicate) {
Assert.notNull(predicate, "Predicate must not be null");
if (this.attributeFilter != null) {
predicate = this.attributeFilter.and(predicate);
}
return new TypeMappedAnnotation<>(this.mapping, this.source, this.rootAttributes,
this.valueExtractor, this.aggregateIndex, this.useMergedValues, predicate,
this.resolvedRootMirrors, this.resolvedMirrors);
}
@Override
public MergedAnnotation<A> withNonMergedAttributes() {
return new TypeMappedAnnotation<>(this.mapping, this.source, this.rootAttributes,
this.valueExtractor, this.aggregateIndex, false, this.attributeFilter,
this.resolvedRootMirrors, this.resolvedMirrors);
}
@Override
@SuppressWarnings("unchecked")
public <T extends Map<String, Object>> T asMap(
@Nullable Function<MergedAnnotation<?>, T> factory, MapValues... options) {
T map = factory != null ? factory.apply(this) : (T) new LinkedHashMap<String, Object>();
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));
if (value != null) {
map.put(attribute.getName(),
adaptValueForMapOptions(attribute, value, factory, options));
}
}
return (factory != null) ? map : (T) Collections.unmodifiableMap(map);
}
private Class<?> getTypeForMapOptions(Method attribute, MapValues[] options) {
Class<?> attributeType = attribute.getReturnType();
Class<?> componentType = attributeType.isArray() ?
attributeType.getComponentType() :
attributeType;
if (MapValues.CLASS_TO_STRING.isIn(options) && 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,
@Nullable Function<MergedAnnotation<?>, T> factory, MapValues[] options) {
if (value instanceof MergedAnnotation) {
MergedAnnotation<?> annotation = (MergedAnnotation<?>) value;
return MapValues.ANNOTATION_TO_MAP.isIn(options) ?
annotation.asMap(factory, options) :
annotation.synthesize();
}
if (value instanceof MergedAnnotation[]) {
MergedAnnotation<?>[] annotations = (MergedAnnotation<?>[]) value;
if (MapValues.ANNOTATION_TO_MAP.isIn(options)) {
Class<?> componentType = Map.class;
if (factory != null) {
componentType = factory.apply(this).getClass();
}
Object result = Array.newInstance(componentType, annotations.length);
for (int i = 0; i < annotations.length; i++) {
Array.set(result, i, annotations[i].asMap(factory, options));
}
return result;
}
Object result = Array.newInstance(
attribute.getReturnType().getComponentType(), annotations.length);
for (int i = 0; i < annotations.length; i++) {
Array.set(result, i, annotations[i].synthesize());
}
return result;
}
return value;
}
@Override
protected A createSynthesized() {
return SynthesizedMergedAnnotationInvocationHandler.createProxy(this, getAnnotationType());
}
@Override
public String toString() {
String string = this.string;
if (string == null) {
StringBuilder builder = new StringBuilder();
builder.append("@");
builder.append(getType());
builder.append("(");
for (int i = 0; i < this.mapping.getAttributes().size(); i++) {
Method attribute = this.mapping.getAttributes().get(i);
builder.append(i == 0 ? "" : ", ");
builder.append(attribute.getName());
builder.append("=");
builder.append(toString(getValue(i, Object.class)));
}
builder.append(")");
string = builder.toString();
this.string = string;
}
return string;
}
private Object toString(@Nullable Object value) {
if (value == null) {
return "";
}
if (value instanceof Class) {
return ((Class<?>) value).getName();
}
if (value.getClass().isArray()) {
StringBuilder builder = new StringBuilder();
builder.append("[");
for (int i = 0; i < Array.getLength(value); i++) {
builder.append(i == 0 ? "" : ", ");
builder.append(toString(Array.get(value, i)));
}
builder.append("]");
return builder.toString();
}
return String.valueOf(value);
}
@Nullable
protected <T> T getAttributeValue(String attributeName, Class<T> type) {
int attributeIndex = getAttributeIndex(attributeName, false);
return attributeIndex != -1 ? getValue(attributeIndex, type) : null;
}
protected final <T> T getRequiredValue(int attributeIndex, Class<T> type) {
T value = getValue(attributeIndex, type);
if (value == null) {
throw new NoSuchElementException(
"No element at attribute index " + attributeIndex);
}
return value;
}
@Nullable
private <T> T getValue(int attributeIndex, Class<T> type) {
Method attribute = this.mapping.getAttributes().get(attributeIndex);
Object value = getValue(attributeIndex, true, true);
if (value == null) {
value = attribute.getDefaultValue();
}
return adapt(attribute, value, type);
}
@Nullable
private Object getValue(int attributeIndex, boolean useConventionMapping,
boolean resolveMirrors) {
AnnotationTypeMapping mapping = this.mapping;
if (this.useMergedValues) {
int mappedIndex = this.mapping.getAliasMapping(attributeIndex);
if (mappedIndex == -1 && useConventionMapping) {
mappedIndex = this.mapping.getConventionMapping(attributeIndex);
}
if (mappedIndex != -1) {
mapping = mapping.getRoot();
attributeIndex = mappedIndex;
}
}
if (resolveMirrors) {
attributeIndex = (mapping.getDepth() != 0 ?
this.resolvedMirrors :
this.resolvedRootMirrors)[attributeIndex];
}
if (attributeIndex == -1) {
return null;
}
Method attribute = mapping.getAttributes().get(attributeIndex);
if (mapping.getDepth() == 0) {
return this.valueExtractor.apply(attribute, this.rootAttributes);
}
return getValueFromMetaAnnotation(attribute);
}
@Nullable
private Object getValueFromMetaAnnotation(Method attribute) {
AnnotationTypeMapping mapping = this.mapping;
if (this.useMergedValues && !VALUE.equals(attribute.getName())) {
AnnotationTypeMapping candidate = mapping;
while (candidate != null && candidate.getDepth() > 0) {
int attributeIndex = candidate.getAttributes().indexOf(attribute.getName());
if (attributeIndex != -1) {
Method candidateAttribute = candidate.getAttributes().get(attributeIndex);
if (candidateAttribute.getReturnType().equals(attribute.getReturnType())) {
mapping = candidate;
attribute = candidateAttribute;
}
}
candidate = candidate.getParent();
}
}
return ReflectionUtils.invokeMethod(attribute, mapping.getAnnotation());
}
@SuppressWarnings("unchecked")
@Nullable
private <T> T adapt(Method attribute, @Nullable Object value, Class<T> type) {
if (value == null) {
return null;
}
value = adaptForAttribute(attribute, value);
if (type == Object.class) {
type = (Class<T>) getDefaultAdaptType(attribute);
}
else if (value instanceof Class && type == String.class) {
value = ((Class<?>) value).getName();
}
else if (value instanceof Class[] && type == String[].class) {
Class<?>[] classes = (Class[]) value;
String[] names = new String[classes.length];
for (int i = 0; i < classes.length; i++) {
names[i] = classes[i].getName();
}
value = names;
}
else if (value instanceof MergedAnnotation && type.isAnnotation()) {
MergedAnnotation<?> annotation = (MergedAnnotation<?>) value;
value = annotation.synthesize();
}
else if (value instanceof MergedAnnotation[] && type.isArray()
&& type.getComponentType().isAnnotation()) {
MergedAnnotation<?>[] annotations = (MergedAnnotation<?>[]) value;
Object array = Array.newInstance(type.getComponentType(), annotations.length);
for (int i = 0; i < annotations.length; i++) {
Array.set(array, i, annotations[i].synthesize());
}
value = array;
}
if (!type.isInstance(value)) {
throw new IllegalArgumentException("Unable to adapt value of type " +
value.getClass().getName() + " to " + type.getName());
}
return (T) value;
}
@SuppressWarnings("unchecked")
private Object adaptForAttribute(Method attribute, Object value) {
Class<?> attributeType = ClassUtils.resolvePrimitiveIfNecessary(attribute.getReturnType());
if (attributeType.isArray() && !value.getClass().isArray()) {
Object array = Array.newInstance(value.getClass(), 1);
Array.set(array, 0, value);
return adaptForAttribute(attribute, array);
}
if (attributeType.isAnnotation()) {
return adaptToMergedAnnotation(value,(Class<? extends Annotation>) attributeType);
}
if (attributeType.isArray() &&
attributeType.getComponentType().isAnnotation() &&
value.getClass().isArray()) {
MergedAnnotation<?>[] result = new MergedAnnotation<?>[Array.getLength(value)];
for (int i = 0; i < result.length; i++) {
result[i] = adaptToMergedAnnotation(Array.get(value, i),
(Class<? extends Annotation>) attributeType.getComponentType());
}
return result;
}
if ((attributeType == Class.class && value instanceof String) ||
(attributeType == Class[].class && value instanceof String[])) {
return value;
}
if (!attributeType.isInstance(value)) {
throw new IllegalStateException("Attribute '" + attribute.getName() +
"' in annotation " + getType() + " should be compatible with " +
attributeType.getName() + " but a " + value.getClass().getName() +
" value was returned");
}
return value;
}
private MergedAnnotation<?> adaptToMergedAnnotation(Object value,
Class<? extends Annotation> annotationType) {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(annotationType);
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(
annotationType, filter).get(0);
return new TypeMappedAnnotation<>(mapping, this.source, value,
this.getValueExtractor(value), this.aggregateIndex);
}
private BiFunction<Method, Object, Object> getValueExtractor(Object value) {
if (value instanceof Annotation) {
return ReflectionUtils::invokeMethod;
}
if (value instanceof Map) {
return TypeMappedAnnotation::extractFromMap;
}
return this.valueExtractor;
}
private Class<?> getDefaultAdaptType(Method attribute) {
Class<?> attributeType = attribute.getReturnType();
if (attributeType.isAnnotation()) {
return MergedAnnotation.class;
}
if (attributeType.isArray() && attributeType.getComponentType().isAnnotation()) {
return MergedAnnotation[].class;
}
return ClassUtils.resolvePrimitiveIfNecessary(attributeType);
}
private int getAttributeIndex(String attributeName, boolean required) {
Assert.hasText(attributeName, "AttributeName must not be null");
int attributeIndex = isFiltered(attributeName) ?
-1 :
this.mapping.getAttributes().indexOf(attributeName);
if (attributeIndex == -1 && required) {
throw new NoSuchElementException("No attribute named '" + attributeName +
"' present in merged annotation " + getType());
}
return attributeIndex;
}
private boolean isFiltered(String attributeName) {
if (this.attributeFilter != null) {
return !this.attributeFilter.test(attributeName);
}
return false;
}
@SuppressWarnings("unchecked")
private Class<A> getAnnotationType() {
return (Class<A>) this.mapping.getAnnotationType();
}
static <A extends Annotation> MergedAnnotation<A> from(@Nullable Object source,
A annotation) {
Assert.notNull(annotation, "Annotation must not be null");
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
annotation.annotationType());
return new TypeMappedAnnotation<>(mappings.get(0), source, annotation,
ReflectionUtils::invokeMethod, 0);
}
static <A extends Annotation> MergedAnnotation<A> from(@Nullable Object source,
Class<A> annotationType, @Nullable Map<String, ?> attributes) {
Assert.notNull(annotationType, "AnnotationType must not be null");
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotationType);
return new TypeMappedAnnotation<>(mappings.get(0), source, attributes,
TypeMappedAnnotation::extractFromMap, 0);
}
@Nullable
static <A extends Annotation> TypeMappedAnnotation<A> createIfPossible(
AnnotationTypeMapping mapping, @Nullable Object source, Annotation annotation,
int aggregateIndex, IntrospectionFailureLogger logger) {
try {
return new TypeMappedAnnotation<>(mapping, source, annotation,
ReflectionUtils::invokeMethod, aggregateIndex);
}
catch (Exception ex) {
if (ex instanceof AnnotationConfigurationException) {
throw (AnnotationConfigurationException) ex;
}
if (logger.isEnabled()) {
String type = mapping.getAnnotationType().getName();
String item = mapping.getDepth() == 0 ?
"annotation " + type :
"meta-annotation " + type + " from " + mapping.getRoot().getAnnotationType().getName();
logger.log("Failed to introspect " + item, source, ex);
}
return null;
}
}
@SuppressWarnings("unchecked")
@Nullable
private static Object extractFromMap(Method attribute, @Nullable Object map) {
return map != null ? ((Map<String, ?>) map).get(attribute.getName()) : null;
}
}

View File

@@ -0,0 +1,697 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.lang.Nullable;
/**
* {@link MergedAnnotations} implementation that searches for and adapts
* annotations and meta-annotations using {@link AnnotationTypeMappings}.
*
* @author Phillip Webb
* @since 5.1
*/
final class TypeMappedAnnotations implements MergedAnnotations {
private static final AnnotationFilter FILTER_ALL = annotationType -> true;
private static final MergedAnnotations NONE = new TypeMappedAnnotations(null,
new Annotation[0], RepeatableContainers.none(), FILTER_ALL);
@Nullable
private final Object source;
@Nullable
private final AnnotatedElement element;
@Nullable
private final SearchStrategy searchStrategy;
@Nullable
private final Annotation[] annotations;
private final RepeatableContainers repeatableContainers;
private final AnnotationFilter annotationFilter;
@Nullable
private volatile List<Aggregate> aggregates;
private TypeMappedAnnotations(AnnotatedElement element, SearchStrategy searchStrategy,
RepeatableContainers repeatableContainers,
AnnotationFilter annotationFilter) {
this.source = element;
this.element = element;
this.searchStrategy = searchStrategy;
this.annotations = null;
this.repeatableContainers = repeatableContainers;
this.annotationFilter = annotationFilter;
}
private TypeMappedAnnotations(@Nullable Object source, Annotation[] annotations,
RepeatableContainers repeatableContainers,
AnnotationFilter annotationFilter) {
this.source = source;
this.element = null;
this.searchStrategy = null;
this.annotations = annotations;
this.repeatableContainers = repeatableContainers;
this.annotationFilter = annotationFilter;
}
@Override
public <A extends Annotation> boolean isPresent(@Nullable Class<A> annotationType) {
if (annotationType == null || this.annotationFilter.matches(annotationType)) {
return false;
}
return Boolean.TRUE.equals(scan(annotationType,
IsPresent.get(this.repeatableContainers, this.annotationFilter, false)));
}
@Override
public boolean isPresent(@Nullable String annotationType) {
if (annotationType == null || this.annotationFilter.matches(annotationType)) {
return false;
}
return Boolean.TRUE.equals(scan(annotationType,
IsPresent.get(this.repeatableContainers, this.annotationFilter, false)));
}
@Override
public <A extends Annotation> boolean isDirectlyPresent(@Nullable Class<A> annotationType) {
if (annotationType == null || this.annotationFilter.matches(annotationType)) {
return false;
}
return Boolean.TRUE.equals(scan(annotationType,
IsPresent.get(this.repeatableContainers, this.annotationFilter, true)));
}
@Override
public boolean isDirectlyPresent(@Nullable String annotationType) {
if (annotationType == null || this.annotationFilter.matches(annotationType)) {
return false;
}
return Boolean.TRUE.equals(scan(annotationType,
IsPresent.get(this.repeatableContainers, this.annotationFilter, true)));
}
@Override
public <A extends Annotation> MergedAnnotation<A> get(
@Nullable Class<A> annotationType) {
return get(annotationType, null, null);
}
@Override
public <A extends Annotation> MergedAnnotation<A> get(
@Nullable Class<A> annotationType,
@Nullable Predicate<? super MergedAnnotation<A>> predicate) {
return get(annotationType, predicate, null);
}
@Override
public <A extends Annotation> MergedAnnotation<A> get(
@Nullable Class<A> annotationType,
@Nullable Predicate<? super MergedAnnotation<A>> predicate,
@Nullable MergedAnnotationSelector<A> selector) {
if (annotationType == null || this.annotationFilter.matches(annotationType)) {
return MergedAnnotation.missing();
}
MergedAnnotation<A> result = scan(annotationType,
new MergedAnnotationFinder<>(annotationType, predicate, selector));
return result != null ? result : MergedAnnotation.missing();
}
@Override
public <A extends Annotation> MergedAnnotation<A> get(
@Nullable String annotationType) {
return get(annotationType, null, null);
}
@Override
public <A extends Annotation> MergedAnnotation<A> get(@Nullable String annotationType,
@Nullable Predicate<? super MergedAnnotation<A>> predicate) {
return get(annotationType, predicate, null);
}
@Override
public <A extends Annotation> MergedAnnotation<A> get(@Nullable String annotationType,
@Nullable Predicate<? super MergedAnnotation<A>> predicate,
@Nullable MergedAnnotationSelector<A> selector) {
if (annotationType == null || this.annotationFilter.matches(annotationType)) {
return MergedAnnotation.missing();
}
MergedAnnotation<A> result = scan(annotationType,
new MergedAnnotationFinder<>(annotationType, predicate, selector));
return result != null ? result : MergedAnnotation.missing();
}
@Override
public <A extends Annotation> Stream<MergedAnnotation<A>> stream(
@Nullable Class<A> annotationType) {
if (this.annotationFilter == FILTER_ALL) {
return Stream.empty();
}
return StreamSupport.stream(spliterator(annotationType), false);
}
@Override
public <A extends Annotation> Stream<MergedAnnotation<A>> stream(
@Nullable String annotationType) {
if (this.annotationFilter == FILTER_ALL) {
return Stream.empty();
}
return StreamSupport.stream(spliterator(annotationType), false);
}
@Override
public Stream<MergedAnnotation<Annotation>> stream() {
if (this.annotationFilter == FILTER_ALL) {
return Stream.empty();
}
return StreamSupport.stream(spliterator(), false);
}
@Override
public Iterator<MergedAnnotation<Annotation>> iterator() {
if (this.annotationFilter == FILTER_ALL) {
return Collections.emptyIterator();
}
return Spliterators.iterator(spliterator());
}
@Override
public Spliterator<MergedAnnotation<Annotation>> spliterator() {
if (this.annotationFilter == FILTER_ALL) {
return Collections.<MergedAnnotation<Annotation>> emptyList().spliterator();
}
return spliterator(null);
}
private <A extends Annotation> Spliterator<MergedAnnotation<A>> spliterator(
@Nullable Object annotationType) {
return new AggregatesSpliterator<>(annotationType, getAggregates());
}
private List<Aggregate> getAggregates() {
List<Aggregate> aggregates = this.aggregates;
if (aggregates == null) {
aggregates = scan(this, new AggregatesCollector());
if (aggregates == null || aggregates.isEmpty()) {
aggregates = Collections.emptyList();
}
this.aggregates = aggregates;
}
return aggregates;
}
@Nullable
private <C, R> R scan(C criteria, AnnotationsProcessor<C, R> processor) {
if (this.annotations != null) {
R result = processor.doWithAnnotations(criteria, 0, this.source, this.annotations);
return processor.finish(result);
}
if (this.element != null && this.searchStrategy != null) {
return AnnotationsScanner.scan(criteria, this.element, this.searchStrategy, processor);
}
return null;
}
static MergedAnnotations from(@Nullable AnnotatedElement element,
SearchStrategy searchStrategy, RepeatableContainers repeatableContainers,
AnnotationFilter annotationFilter) {
if (element == null || AnnotationsScanner.isKnownEmpty(element, searchStrategy, annotationFilter)) {
return NONE;
}
return new TypeMappedAnnotations(element, searchStrategy, repeatableContainers,
annotationFilter);
}
static MergedAnnotations from(@Nullable Object source,
Annotation[] annotations, RepeatableContainers repeatableContainers,
AnnotationFilter annotationFilter) {
if (annotations.length == 0) {
return NONE;
}
return new TypeMappedAnnotations(source, annotations, repeatableContainers, annotationFilter);
}
private static boolean isMappingForType(@Nullable AnnotationTypeMapping mapping,
AnnotationFilter annotationFilter, @Nullable Object requiredType) {
if (mapping == null) {
return false;
}
Class<? extends Annotation> actualType = mapping.getAnnotationType();
return !annotationFilter.matches(actualType) &&
(requiredType == null || actualType == requiredType || actualType.getName().equals(requiredType));
}
/**
* {@link AnnotationsProcessor} used to detect if an annotation is directly
* or meta-present.
*/
private static final class IsPresent
implements AnnotationsProcessor<Object, Boolean> {
/**
* Shared instances that save us needing to create a new processor for
* the common combinations.
*/
private static final IsPresent[] SHARED;
static {
SHARED = new IsPresent[4];
SHARED[0] = new IsPresent(RepeatableContainers.none(), AnnotationFilter.PLAIN, true);
SHARED[1] = new IsPresent(RepeatableContainers.none(), AnnotationFilter.PLAIN, false);
SHARED[2] = new IsPresent(RepeatableContainers.standardRepeatables(), AnnotationFilter.PLAIN, true);
SHARED[3] = new IsPresent(RepeatableContainers.standardRepeatables(), AnnotationFilter.PLAIN, false);
}
private final RepeatableContainers repeatableContainers;
private final AnnotationFilter annotationFilter;
private final boolean directOnly;
private IsPresent(RepeatableContainers repeatableContainers,
AnnotationFilter annotationFilter, boolean directOnly) {
this.repeatableContainers = repeatableContainers;
this.annotationFilter = annotationFilter;
this.directOnly = directOnly;
}
@Override
@Nullable
public Boolean doWithAnnotations(Object requiredType, int aggregateIndex,
@Nullable Object source, Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation != null) {
Class<? extends Annotation> type = annotation.annotationType();
if (type != null && !this.annotationFilter.matches(type)) {
if (type == requiredType || type.getName().equals(requiredType)) {
return Boolean.TRUE;
}
Annotation[] repeatedAnnotations = this.repeatableContainers
.findRepeatedAnnotations(annotation);
if (repeatedAnnotations != null) {
Boolean result = doWithAnnotations(requiredType, aggregateIndex,
source, repeatedAnnotations);
if (result != null) {
return result;
}
}
if (!this.directOnly) {
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(type);
for (int i = 0; i < mappings.size(); i++) {
AnnotationTypeMapping mapping = mappings.get(i);
if (isMappingForType(mapping, this.annotationFilter, requiredType)) {
return Boolean.TRUE;
}
}
}
}
}
}
return null;
}
static IsPresent get(RepeatableContainers repeatableContainers,
AnnotationFilter annotationFilter, boolean directOnly) {
// Use a single shared instance for common combinations
if (annotationFilter == AnnotationFilter.PLAIN) {
if (repeatableContainers == RepeatableContainers.none()) {
return SHARED[directOnly ? 0 : 1];
}
if (repeatableContainers == RepeatableContainers.standardRepeatables()) {
return SHARED[directOnly ? 2 : 3];
}
}
return new IsPresent(repeatableContainers, annotationFilter, directOnly);
}
}
/**
* {@link AnnotationsProcessor} that finds a single
* {@link MergedAnnotation}.
*/
private class MergedAnnotationFinder<A extends Annotation>
implements AnnotationsProcessor<Object, MergedAnnotation<A>> {
private final Object requiredType;
@Nullable
private final Predicate<? super MergedAnnotation<A>> predicate;
private final MergedAnnotationSelector<A> selector;
@Nullable
private MergedAnnotation<A> result;
MergedAnnotationFinder(Object requiredType,
@Nullable Predicate<? super MergedAnnotation<A>> predicate,
@Nullable MergedAnnotationSelector<A> selector) {
this.requiredType = requiredType;
this.predicate = predicate;
this.selector = selector != null ? selector
: MergedAnnotationSelectors.nearest();
}
@Override
@Nullable
public MergedAnnotation<A> doWithAggregate(Object context, int aggregateIndex) {
return this.result;
}
@Override
@Nullable
public MergedAnnotation<A> doWithAnnotations(Object type, int aggregateIndex,
@Nullable Object source, Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation != null &&
!annotationFilter.matches(annotation)) {
MergedAnnotation<A> result = process(type, aggregateIndex, source, annotation);
if (result != null) {
return result;
}
}
}
return null;
}
@Nullable
private MergedAnnotation<A> process(Object type, int aggregateIndex,
@Nullable Object source, Annotation annotation) {
Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(
annotation);
if (repeatedAnnotations != null) {
return doWithAnnotations(type, aggregateIndex, source,
repeatedAnnotations);
}
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
annotation.annotationType(),
annotationFilter);
for (int i = 0; i < mappings.size(); i++) {
AnnotationTypeMapping mapping = mappings.get(i);
if (isMappingForType(mapping, annotationFilter,
this.requiredType)) {
MergedAnnotation<A> candidate = TypeMappedAnnotation.createIfPossible(
mapping, source, annotation, aggregateIndex,
IntrospectionFailureLogger.INFO);
if (candidate != null && (this.predicate == null
|| this.predicate.test(candidate))) {
if (this.selector.isBestCandidate(candidate)) {
return candidate;
}
updateLastResult(candidate);
}
}
}
return null;
}
private void updateLastResult(MergedAnnotation<A> candidate) {
MergedAnnotation<A> lastResult = this.result;
this.result = lastResult != null
? this.selector.select(lastResult, candidate)
: candidate;
}
@Override
@Nullable
public MergedAnnotation<A> finish(@Nullable MergedAnnotation<A> result) {
return result != null ? result : this.result;
}
}
/**
* {@link AnnotationsProcessor} that collects {@link Aggregate} instances.
*/
private class AggregatesCollector
implements AnnotationsProcessor<Object, List<Aggregate>> {
private final List<Aggregate> aggregates = new ArrayList<>();
@Override
@Nullable
public List<Aggregate> doWithAnnotations(Object criteria, int aggregateIndex,
@Nullable Object source, Annotation[] annotations) {
this.aggregates.add(createAggregate(aggregateIndex, source, annotations));
return null;
}
private Aggregate createAggregate(int aggregateIndex, @Nullable Object source,
Annotation[] annotations) {
List<Annotation> aggregateAnnotations = getAggregateAnnotations(annotations);
return new Aggregate(aggregateIndex, source, aggregateAnnotations);
}
private List<Annotation> getAggregateAnnotations(Annotation[] annotations) {
List<Annotation> result = new ArrayList<>(annotations.length);
addAggregateAnnotations(result, annotations);
return result;
}
private void addAggregateAnnotations(List<Annotation> aggregateAnnotations,
Annotation[] annotations) {
for (Annotation annotation : annotations) {
if (annotation != null
&& !annotationFilter.matches(
annotation)) {
Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(
annotation);
if (repeatedAnnotations != null) {
addAggregateAnnotations(aggregateAnnotations,
repeatedAnnotations);
}
else {
aggregateAnnotations.add(annotation);
}
}
}
}
@Override
public List<Aggregate> finish(@Nullable List<Aggregate> processResult) {
return this.aggregates;
}
}
private static class Aggregate {
private final int aggregateIndex;
@Nullable
private final Object source;
private final List<Annotation> annotations;
private final AnnotationTypeMappings[] mappings;
Aggregate(int aggregateIndex, @Nullable Object source,
List<Annotation> annotations) {
this.aggregateIndex = aggregateIndex;
this.source = source;
this.annotations = annotations;
this.mappings = new AnnotationTypeMappings[annotations.size()];
for (int i = 0; i < annotations.size(); i++) {
this.mappings[i] = AnnotationTypeMappings.forAnnotationType(
annotations.get(i).annotationType());
}
}
int size() {
return this.annotations.size();
}
@Nullable
AnnotationTypeMapping getMapping(int annotationIndex, int mappingIndex) {
AnnotationTypeMappings mappings = getMappings(annotationIndex);
return mappingIndex < mappings.size() ? mappings.get(mappingIndex) : null;
}
AnnotationTypeMappings getMappings(int annotationIndex) {
return this.mappings[annotationIndex];
}
@Nullable
<A extends Annotation> MergedAnnotation<A> createMergedAnnotationIfPossible(
int annotationIndex, int mappingIndex,
IntrospectionFailureLogger logger) {
return TypeMappedAnnotation.createIfPossible(
this.mappings[annotationIndex].get(mappingIndex), this.source,
this.annotations.get(annotationIndex), this.aggregateIndex, logger);
}
}
/**
* {@link Spliterator} used to consume merged annotations from the
* aggregates in depth fist order.
*/
private class AggregatesSpliterator<A extends Annotation>
implements Spliterator<MergedAnnotation<A>> {
@Nullable
private final Object requiredType;
private final List<Aggregate> aggregates;
private int aggregateCursor;
@Nullable
private int[] mappingCursors;
AggregatesSpliterator(@Nullable Object requiredType,
List<Aggregate> aggregates) {
this.requiredType = requiredType;
this.aggregates = aggregates;
this.aggregateCursor = 0;
}
public boolean tryAdvance(Consumer<? super MergedAnnotation<A>> action) {
while (this.aggregateCursor < this.aggregates.size()) {
Aggregate aggregate = this.aggregates.get(this.aggregateCursor);
if (tryAdvance(aggregate, action)) {
return true;
}
this.aggregateCursor++;
this.mappingCursors = null;
}
return false;
}
private boolean tryAdvance(Aggregate aggregate,
Consumer<? super MergedAnnotation<A>> action) {
if (this.mappingCursors == null) {
this.mappingCursors = new int[aggregate.size()];
}
int lowestDepth = Integer.MAX_VALUE;
int annotationResult = -1;
for (int annotationIndex = 0; annotationIndex < aggregate.size(); annotationIndex++) {
AnnotationTypeMapping mapping = getNextSuitableMapping(aggregate, annotationIndex);
if (mapping != null && mapping.getDepth() < lowestDepth) {
annotationResult = annotationIndex;
lowestDepth = mapping.getDepth();
}
if (lowestDepth == 0) {
break;
}
}
if (annotationResult != -1) {
MergedAnnotation<A> mergedAnnotation = aggregate.createMergedAnnotationIfPossible(
annotationResult, this.mappingCursors[annotationResult],
this.requiredType != null ? IntrospectionFailureLogger.INFO : IntrospectionFailureLogger.DEBUG);
this.mappingCursors[annotationResult]++;
if (mergedAnnotation == null) {
return tryAdvance(aggregate, action);
}
action.accept(mergedAnnotation);
return true;
}
return false;
}
@Nullable
private AnnotationTypeMapping getNextSuitableMapping(Aggregate aggregate,
int annotationIndex) {
int[] cursors = this.mappingCursors;
if (cursors != null) {
AnnotationTypeMapping mapping;
do {
mapping = aggregate.getMapping(annotationIndex, cursors[annotationIndex]);
if (isMappingForType(mapping, annotationFilter, this.requiredType)) {
return mapping;
}
cursors[annotationIndex]++;
}
while (mapping != null);
}
return null;
}
@Override
@Nullable
public Spliterator<MergedAnnotation<A>> trySplit() {
return null;
}
@Override
public long estimateSize() {
int size = 0;
for (int aggregateIndex = this.aggregateCursor;
aggregateIndex < this.aggregates.size(); aggregateIndex++) {
Aggregate aggregate = this.aggregates.get(aggregateIndex);
for (int annotationIndex = 0; annotationIndex < aggregate.size(); annotationIndex++) {
AnnotationTypeMappings mappings = aggregate.getMappings(annotationIndex);
int numberOfMappings = mappings.size();
if (aggregateIndex == this.aggregateCursor && this.mappingCursors != null) {
numberOfMappings -= Math.min(this.mappingCursors[annotationIndex], mappings.size());
}
size += numberOfMappings;
}
}
return size;
}
@Override
public int characteristics() {
return NONNULL | IMMUTABLE;
}
}
}