org.springframework.core.annotation.RepeatableContainers Maven / Gradle / Ivy
/*
* 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
*
* 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.annotation.Repeatable;
import java.lang.reflect.Method;
import java.util.Map;
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.
*
* The {@link #of} method can be used to register relationships for
* annotations that do not wish to use {@link Repeatable @Repeatable}.
*
*
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 container,
Class 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 other) {
if (other == this) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
return ObjectUtils.nullSafeEquals(this.parent, ((RepeatableContainers) other).parent);
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.parent);
}
/**
* Create 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;
}
/**
* Create 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 repeatable, @Nullable Class container) {
return new ExplicitRepeatableContainer(null, repeatable, container);
}
/**
* Create 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, 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 annotationType) {
Object result = cache.computeIfAbsent(annotationType,
StandardRepeatableContainers::computeRepeatedAnnotationsMethod);
return (result != NONE ? (Method) result : null);
}
private static Object computeRepeatedAnnotationsMethod(Class annotationType) {
AttributeMethods methods = AttributeMethods.forAnnotationType(annotationType);
if (methods.hasOnlyValueAttribute()) {
Method method = methods.get(0);
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 repeatable;
private final Class container;
private final Method valueMethod;
ExplicitRepeatableContainer(@Nullable RepeatableContainers parent,
Class repeatable, @Nullable Class container) {
super(parent);
Assert.notNull(repeatable, "Repeatable must not be null");
if (container == null) {
container = deduceContainer(repeatable);
}
Method valueMethod = AttributeMethods.forAnnotationType(container).get(MergedAnnotation.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 deduceContainer(Class 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 other) {
if (!super.equals(other)) {
return false;
}
ExplicitRepeatableContainer otherErc = (ExplicitRepeatableContainer) other;
return (this.container.equals(otherErc.container) && this.repeatable.equals(otherErc.repeatable));
}
@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);
}
}
}