All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.core.annotation.TypeMappedAnnotation Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2022 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
 *
 *      https://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.Member;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
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;
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}.
 *
 * 

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 {@linkplain 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)}. * *

Extracted root attribute values must be compatible with the attribute * return type, namely: * *

* * * * * * *
Return TypeExtracted Type
ClassClass or String
Class[]Class[] or String[]
AnnotationAnnotation, Map, or Object compatible with the value * extractor
Annotation[]Annotation[], Map[], or Object[] where elements are * compatible with the value extractor
Other typesAn exact match or the appropriate primitive wrapper
* * @author Phillip Webb * @author Juergen Hoeller * @author Sam Brannen * @since 5.2 * @param the annotation type * @see TypeMappedAnnotations */ final class TypeMappedAnnotation extends AbstractMergedAnnotation { private static final Map, Object> EMPTY_ARRAYS; static { Map, Object> emptyArrays = new HashMap<>(); emptyArrays.put(boolean.class, new boolean[0]); emptyArrays.put(byte.class, new byte[0]); emptyArrays.put(char.class, new char[0]); emptyArrays.put(double.class, new double[0]); emptyArrays.put(float.class, new float[0]); emptyArrays.put(int.class, new int[0]); emptyArrays.put(long.class, new long[0]); emptyArrays.put(short.class, new short[0]); emptyArrays.put(String.class, new String[0]); EMPTY_ARRAYS = Collections.unmodifiableMap(emptyArrays); } private final AnnotationTypeMapping mapping; @Nullable private final ClassLoader classLoader; @Nullable private final Object source; @Nullable private final Object rootAttributes; private final ValueExtractor valueExtractor; private final int aggregateIndex; private final boolean useMergedValues; @Nullable private final Predicate attributeFilter; private final int[] resolvedRootMirrors; private final int[] resolvedMirrors; private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable ClassLoader classLoader, @Nullable Object source, @Nullable Object rootAttributes, ValueExtractor valueExtractor, int aggregateIndex) { this(mapping, classLoader, source, rootAttributes, valueExtractor, aggregateIndex, null); } private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable ClassLoader classLoader, @Nullable Object source, @Nullable Object rootAttributes, ValueExtractor valueExtractor, int aggregateIndex, @Nullable int[] resolvedRootMirrors) { this.mapping = mapping; this.classLoader = classLoader; this.source = source; this.rootAttributes = rootAttributes; this.valueExtractor = valueExtractor; this.aggregateIndex = aggregateIndex; this.useMergedValues = true; this.attributeFilter = null; this.resolvedRootMirrors = (resolvedRootMirrors != null ? resolvedRootMirrors : mapping.getRoot().getMirrorSets().resolve(source, rootAttributes, this.valueExtractor)); this.resolvedMirrors = (getDistance() == 0 ? this.resolvedRootMirrors : mapping.getMirrorSets().resolve(source, this, this::getValueForMirrorResolution)); } private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable ClassLoader classLoader, @Nullable Object source, @Nullable Object rootAnnotation, ValueExtractor valueExtractor, int aggregateIndex, boolean useMergedValues, @Nullable Predicate attributeFilter, int[] resolvedRootMirrors, int[] resolvedMirrors) { this.classLoader = classLoader; 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; } @Override @SuppressWarnings("unchecked") public Class getType() { return (Class) this.mapping.getAnnotationType(); } @Override public List> getMetaTypes() { return this.mapping.getMetaTypes(); } @Override public boolean isPresent() { return true; } @Override public int getDistance() { return this.mapping.getDistance(); } @Override public int getAggregateIndex() { return this.aggregateIndex; } @Override @Nullable public Object getSource() { return this.source; } @Override @Nullable public MergedAnnotation getMetaSource() { AnnotationTypeMapping metaSourceMapping = this.mapping.getSource(); if (metaSourceMapping == null) { return null; } return new TypeMappedAnnotation<>(metaSourceMapping, this.classLoader, this.source, this.rootAttributes, this.valueExtractor, this.aggregateIndex, this.resolvedRootMirrors); } @Override public MergedAnnotation getRoot() { if (getDistance() == 0) { return this; } AnnotationTypeMapping rootMapping = this.mapping.getRoot(); return new TypeMappedAnnotation<>(rootMapping, this.classLoader, 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, false); return (value == null || this.mapping.isEquivalentToDefaultValue(attributeIndex, value, this.valueExtractor)); } @Override @SuppressWarnings("unchecked") public MergedAnnotation getAnnotation(String attributeName, Class type) throws NoSuchElementException { int attributeIndex = getAttributeIndex(attributeName, true); Method attribute = this.mapping.getAttributes().get(attributeIndex); Assert.notNull(type, "Type must not be null"); Assert.isAssignable(type, attribute.getReturnType(), () -> "Attribute " + attributeName + " type mismatch:"); return (MergedAnnotation) getRequiredValue(attributeIndex, attributeName); } @Override @SuppressWarnings("unchecked") public MergedAnnotation[] getAnnotationArray( String attributeName, Class type) throws NoSuchElementException { int attributeIndex = getAttributeIndex(attributeName, true); Method attribute = this.mapping.getAttributes().get(attributeIndex); Class componentType = attribute.getReturnType().getComponentType(); Assert.notNull(type, "Type must not be null"); Assert.notNull(componentType, () -> "Attribute " + attributeName + " is not an array"); Assert.isAssignable(type, componentType, () -> "Attribute " + attributeName + " component type mismatch:"); return (MergedAnnotation[]) getRequiredValue(attributeIndex, attributeName); } @Override public Optional getDefaultValue(String attributeName, Class 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 filterAttributes(Predicate predicate) { if (this.attributeFilter != null) { predicate = this.attributeFilter.and(predicate); } return new TypeMappedAnnotation<>(this.mapping, this.classLoader, this.source, this.rootAttributes, this.valueExtractor, this.aggregateIndex, this.useMergedValues, predicate, this.resolvedRootMirrors, this.resolvedMirrors); } @Override public MergedAnnotation withNonMergedAttributes() { return new TypeMappedAnnotation<>(this.mapping, this.classLoader, this.source, this.rootAttributes, this.valueExtractor, this.aggregateIndex, false, this.attributeFilter, this.resolvedRootMirrors, this.resolvedMirrors); } @Override public Map asMap(Adapt... adaptations) { return Collections.unmodifiableMap(asMap(mergedAnnotation -> new LinkedHashMap<>(), adaptations)); } @Override public > T asMap(Function, T> factory, Adapt... adaptations) { T map = factory.apply(this); Assert.state(map != null, "Factory used to create MergedAnnotation Map must not return null"); AttributeMethods attributes = this.mapping.getAttributes(); for (int i = 0; i < attributes.size(); i++) { Method attribute = attributes.get(i); Object value = (isFiltered(attribute.getName()) ? null : getValue(i, getTypeForMapOptions(attribute, adaptations))); if (value != null) { map.put(attribute.getName(), adaptValueForMapOptions(attribute, value, map.getClass(), factory, adaptations)); } } return map; } private Class getTypeForMapOptions(Method attribute, Adapt[] adaptations) { Class attributeType = attribute.getReturnType(); Class componentType = (attributeType.isArray() ? attributeType.getComponentType() : attributeType); if (Adapt.CLASS_TO_STRING.isIn(adaptations) && componentType == Class.class) { return (attributeType.isArray() ? String[].class : String.class); } return Object.class; } private > Object adaptValueForMapOptions(Method attribute, Object value, Class mapType, Function, T> factory, Adapt[] adaptations) { if (value instanceof MergedAnnotation) { MergedAnnotation annotation = (MergedAnnotation) value; return (Adapt.ANNOTATION_TO_MAP.isIn(adaptations) ? annotation.asMap(factory, adaptations) : annotation.synthesize()); } if (value instanceof MergedAnnotation[]) { MergedAnnotation[] annotations = (MergedAnnotation[]) value; if (Adapt.ANNOTATION_TO_MAP.isIn(adaptations)) { Object result = Array.newInstance(mapType, annotations.length); for (int i = 0; i < annotations.length; i++) { Array.set(result, i, annotations[i].asMap(factory, adaptations)); } 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 @SuppressWarnings("unchecked") protected A createSynthesizedAnnotation() { // Check root annotation if (isTargetAnnotation(this.rootAttributes) && !isSynthesizable((Annotation) this.rootAttributes)) { return (A) this.rootAttributes; } // Check meta-annotation else if (isTargetAnnotation(this.mapping.getAnnotation()) && !isSynthesizable(this.mapping.getAnnotation())) { return (A) this.mapping.getAnnotation(); } return SynthesizedMergedAnnotationInvocationHandler.createProxy(this, getType()); } /** * Determine if the supplied object is an annotation of the required * {@linkplain #getType() type}. * @param obj the object to check * @since 5.3.22 */ private boolean isTargetAnnotation(@Nullable Object obj) { return getType().isInstance(obj); } /** * Determine if the supplied annotation has not already been synthesized * and whether the mapped annotation is a composed annotation * that needs to have its attributes merged or the mapped annotation is * {@linkplain AnnotationTypeMapping#isSynthesizable() synthesizable} in general. * @param annotation the annotation to check * @since 5.3.22 */ private boolean isSynthesizable(Annotation annotation) { // Already synthesized? if (annotation instanceof SynthesizedAnnotation) { return false; } // Is this a mapped annotation for a composed annotation, and are there // annotation attributes (mirrors) that need to be merged? if (getDistance() > 0 && this.resolvedMirrors.length > 0) { return true; } // Is the mapped annotation itself synthesizable? return this.mapping.isSynthesizable(); } @Override @Nullable protected T getAttributeValue(String attributeName, Class type) { int attributeIndex = getAttributeIndex(attributeName, false); return (attributeIndex != -1 ? getValue(attributeIndex, type) : null); } private Object getRequiredValue(int attributeIndex, String attributeName) { Object value = getValue(attributeIndex, Object.class); if (value == null) { throw new NoSuchElementException("No element at attribute index " + attributeIndex + " for name " + attributeName); } return value; } @Nullable private T getValue(int attributeIndex, Class type) { Method attribute = this.mapping.getAttributes().get(attributeIndex); Object value = getValue(attributeIndex, true, false); if (value == null) { value = attribute.getDefaultValue(); } return adapt(attribute, value, type); } @Nullable private Object getValue(int attributeIndex, boolean useConventionMapping, boolean forMirrorResolution) { 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 (!forMirrorResolution) { attributeIndex = (mapping.getDistance() != 0 ? this.resolvedMirrors : this.resolvedRootMirrors)[attributeIndex]; } if (attributeIndex == -1) { return null; } if (mapping.getDistance() == 0) { Method attribute = mapping.getAttributes().get(attributeIndex); Object result = this.valueExtractor.extract(attribute, this.rootAttributes); return (result != null ? result : attribute.getDefaultValue()); } return getValueFromMetaAnnotation(attributeIndex, forMirrorResolution); } @Nullable private Object getValueFromMetaAnnotation(int attributeIndex, boolean forMirrorResolution) { Object value = null; if (this.useMergedValues || forMirrorResolution) { value = this.mapping.getMappedAnnotationValue(attributeIndex, forMirrorResolution); } if (value == null) { Method attribute = this.mapping.getAttributes().get(attributeIndex); value = ReflectionUtils.invokeMethod(attribute, this.mapping.getAnnotation()); } return value; } @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, true); } @SuppressWarnings("unchecked") @Nullable private T adapt(Method attribute, @Nullable Object value, Class type) { if (value == null) { return null; } value = adaptForAttribute(attribute, value); type = getAdaptType(attribute, type); if (value instanceof Class && type == String.class) { value = ((Class) value).getName(); } else if (value instanceof String && type == Class.class) { value = ClassUtils.resolveClassName((String) value, getClassLoader()); } 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 String[] && type == Class[].class) { String[] names = (String[]) value; Class[] classes = new Class[names.length]; for (int i = 0; i < names.length; i++) { classes[i] = ClassUtils.resolveClassName(names[i], getClassLoader()); } value = classes; } 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) attributeType); } if (attributeType.isArray() && attributeType.getComponentType().isAnnotation()) { MergedAnnotation[] result = new MergedAnnotation[Array.getLength(value)]; for (int i = 0; i < result.length; i++) { result[i] = adaptToMergedAnnotation(Array.get(value, i), (Class) attributeType.getComponentType()); } return result; } if ((attributeType == Class.class && value instanceof String) || (attributeType == Class[].class && value instanceof String[]) || (attributeType == String.class && value instanceof Class) || (attributeType == String[].class && value instanceof Class[])) { return value; } if (attributeType.isArray() && isEmptyObjectArray(value)) { return emptyArray(attributeType.getComponentType()); } if (!attributeType.isInstance(value)) { throw new IllegalStateException("Attribute '" + attribute.getName() + "' in annotation " + getType().getName() + " should be compatible with " + attributeType.getName() + " but a " + value.getClass().getName() + " value was returned"); } return value; } private boolean isEmptyObjectArray(Object value) { return (value instanceof Object[] && ((Object[]) value).length == 0); } private Object emptyArray(Class componentType) { Object result = EMPTY_ARRAYS.get(componentType); if (result == null) { result = Array.newInstance(componentType, 0); } return result; } private MergedAnnotation adaptToMergedAnnotation(Object value, Class annotationType) { if (value instanceof MergedAnnotation) { return (MergedAnnotation) value; } AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0); return new TypeMappedAnnotation<>( mapping, null, this.source, value, getValueExtractor(value), this.aggregateIndex); } private ValueExtractor getValueExtractor(Object value) { if (value instanceof Annotation) { return ReflectionUtils::invokeMethod; } if (value instanceof Map) { return TypeMappedAnnotation::extractFromMap; } return this.valueExtractor; } @SuppressWarnings("unchecked") private Class getAdaptType(Method attribute, Class type) { if (type != Object.class) { return type; } Class attributeType = attribute.getReturnType(); if (attributeType.isAnnotation()) { return (Class) MergedAnnotation.class; } if (attributeType.isArray() && attributeType.getComponentType().isAnnotation()) { return (Class) MergedAnnotation[].class; } return (Class) ClassUtils.resolvePrimitiveIfNecessary(attributeType); } private int getAttributeIndex(String attributeName, boolean required) { Assert.hasText(attributeName, "Attribute name 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().getName()); } return attributeIndex; } private boolean isFiltered(String attributeName) { if (this.attributeFilter != null) { return !this.attributeFilter.test(attributeName); } return false; } @Nullable private ClassLoader getClassLoader() { if (this.classLoader != null) { return this.classLoader; } if (this.source != null) { if (this.source instanceof Class) { return ((Class) this.source).getClassLoader(); } if (this.source instanceof Member) { ((Member) this.source).getDeclaringClass().getClassLoader(); } } return null; } static MergedAnnotation 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), null, source, annotation, ReflectionUtils::invokeMethod, 0); } static MergedAnnotation of( @Nullable ClassLoader classLoader, @Nullable Object source, Class annotationType, @Nullable Map attributes) { Assert.notNull(annotationType, "Annotation type must not be null"); AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotationType); return new TypeMappedAnnotation<>( mappings.get(0), classLoader, source, attributes, TypeMappedAnnotation::extractFromMap, 0); } @Nullable static TypeMappedAnnotation createIfPossible( AnnotationTypeMapping mapping, MergedAnnotation annotation, IntrospectionFailureLogger logger) { if (annotation instanceof TypeMappedAnnotation) { TypeMappedAnnotation typeMappedAnnotation = (TypeMappedAnnotation) annotation; return createIfPossible(mapping, typeMappedAnnotation.source, typeMappedAnnotation.rootAttributes, typeMappedAnnotation.valueExtractor, typeMappedAnnotation.aggregateIndex, logger); } return createIfPossible(mapping, annotation.getSource(), annotation.synthesize(), annotation.getAggregateIndex(), logger); } @Nullable static TypeMappedAnnotation createIfPossible( AnnotationTypeMapping mapping, @Nullable Object source, Annotation annotation, int aggregateIndex, IntrospectionFailureLogger logger) { return createIfPossible(mapping, source, annotation, ReflectionUtils::invokeMethod, aggregateIndex, logger); } @Nullable private static TypeMappedAnnotation createIfPossible( AnnotationTypeMapping mapping, @Nullable Object source, @Nullable Object rootAttribute, ValueExtractor valueExtractor, int aggregateIndex, IntrospectionFailureLogger logger) { try { return new TypeMappedAnnotation<>(mapping, null, source, rootAttribute, valueExtractor, aggregateIndex); } catch (Exception ex) { AnnotationUtils.rethrowAnnotationConfigurationException(ex); if (logger.isEnabled()) { String type = mapping.getAnnotationType().getName(); String item = (mapping.getDistance() == 0 ? "annotation " + type : "meta-annotation " + type + " from " + mapping.getRoot().getAnnotationType().getName()); logger.log("Failed to introspect " + item, source, ex); } return null; } } @SuppressWarnings("unchecked") @Nullable static Object extractFromMap(Method attribute, @Nullable Object map) { return (map != null ? ((Map) map).get(attribute.getName()) : null); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy