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

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

There is a newer version: 6.1.11
Show newest version
/*
 * Copyright 2002-2024 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.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;
import org.springframework.util.ReflectionUtils;

/**
 * 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
 * @author Sam Brannen
 * @since 5.2
 */
final class AttributeMethods {

	static final AttributeMethods NONE = new AttributeMethods(null, new Method[0]);

	static final Map, AttributeMethods> cache = new ConcurrentReferenceHashMap<>();

	private static final Comparator methodComparator = (m1, m2) -> {
		if (m1 != null && m2 != null) {
			return m1.getName().compareTo(m2.getName());
		}
		return (m1 != null ? -1 : 1);
	};


	@Nullable
	private final Class annotationType;

	private final Method[] attributeMethods;

	private final boolean[] canThrowTypeNotPresentException;

	private final boolean hasDefaultValueMethod;

	private final boolean hasNestedAnnotation;


	private AttributeMethods(@Nullable Class 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 (!foundDefaultValueMethod && (method.getDefaultValue() != null)) {
				foundDefaultValueMethod = true;
			}
			if (!foundNestedAnnotation && (type.isAnnotation() || (type.isArray() && type.componentType().isAnnotation()))) {
				foundNestedAnnotation = true;
			}
			ReflectionUtils.makeAccessible(method);
			this.canThrowTypeNotPresentException[i] = (type == Class.class || type == Class[].class || type.isEnum());
		}
		this.hasDefaultValueMethod = foundDefaultValueMethod;
		this.hasNestedAnnotation = foundNestedAnnotation;
	}


	/**
	 * Determine if values from the given annotation can be safely accessed without
	 * causing any {@link TypeNotPresentException TypeNotPresentExceptions}.
	 * 

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} on a regular JVM). * @param annotation the annotation to check * @return {@code true} if all values are present * @see #validate(Annotation) */ boolean canLoad(Annotation annotation) { assertAnnotation(annotation); for (int i = 0; i < size(); i++) { if (canThrowTypeNotPresentException(i)) { try { AnnotationUtils.invokeAnnotationMethod(get(i), annotation); } catch (IllegalStateException ex) { // Plain invocation failure to expose -> leave up to attribute retrieval // (if any) where such invocation failure will be logged eventually. } catch (Throwable ex) { // TypeNotPresentException etc. -> annotation type not actually loadable. return false; } } } return true; } /** * Check if values from the given annotation can be safely accessed without causing * any {@link TypeNotPresentException TypeNotPresentExceptions}. *

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} on a regular JVM). * @param annotation the annotation to validate * @throws IllegalStateException if a declared {@code Class} attribute could not be read * @see #canLoad(Annotation) */ void validate(Annotation annotation) { assertAnnotation(annotation); for (int i = 0; i < size(); i++) { if (canThrowTypeNotPresentException(i)) { try { AnnotationUtils.invokeAnnotationMethod(get(i), annotation); } catch (IllegalStateException ex) { throw ex; } 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); } } /** * Get 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); } /** * Get 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 * ({@code index < 0 || index >= size()}) */ Method get(int index) { return this.attributeMethods[index]; } /** * Determine 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]; } /** * Get 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; } /** * Get the index of the specified attribute, or {@code -1} if the * attribute is 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; } /** * Get the number of attributes in this collection. * @return the number of attributes */ int size() { return this.attributeMethods.length; } /** * Determine if at least one of the attribute methods has a default value. * @return {@code true} if there is at least one attribute method with a default value */ boolean hasDefaultValueMethod() { return this.hasDefaultValueMethod; } /** * Determine if at least one of the attribute methods is a nested annotation. * @return {@code true} if there is at least one attribute method with a nested * annotation type */ boolean hasNestedAnnotation() { return this.hasNestedAnnotation; } /** * Get the attribute methods for the given annotation type. * @param annotationType the annotation type * @return the attribute methods for the annotation type */ static AttributeMethods forAnnotationType(@Nullable Class annotationType) { if (annotationType == null) { return NONE; } return cache.computeIfAbsent(annotationType, AttributeMethods::compute); } private static AttributeMethods compute(Class 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 = Arrays.copyOf(methods, size); return new AttributeMethods(annotationType, attributeMethods); } private static boolean isAttributeMethod(Method method) { return (method.getParameterCount() == 0 && method.getReturnType() != void.class); } /** * Create 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()); } /** * Create 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy