001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.core.annotation; 018 019import java.lang.annotation.Annotation; 020import java.lang.annotation.Repeatable; 021import java.lang.reflect.Method; 022import java.util.Map; 023 024import org.springframework.lang.Nullable; 025import org.springframework.util.Assert; 026import org.springframework.util.ConcurrentReferenceHashMap; 027import org.springframework.util.ObjectUtils; 028import org.springframework.util.ReflectionUtils; 029 030/** 031 * Strategy used to determine annotations that act as containers for other 032 * annotations. The {@link #standardRepeatables()} method provides a default 033 * strategy that respects Java's {@link Repeatable @Repeatable} support and 034 * should be suitable for most situations. 035 * 036 * <p>The {@link #of} method can be used to register relationships for 037 * annotations that do not wish to use {@link Repeatable @Repeatable}. 038 * 039 * <p>To completely disable repeatable support use {@link #none()}. 040 * 041 * @author Phillip Webb 042 * @since 5.2 043 */ 044public abstract class RepeatableContainers { 045 046 @Nullable 047 private final RepeatableContainers parent; 048 049 050 private RepeatableContainers(@Nullable RepeatableContainers parent) { 051 this.parent = parent; 052 } 053 054 055 /** 056 * Add an additional explicit relationship between a contained and 057 * repeatable annotation. 058 * @param container the container type 059 * @param repeatable the contained repeatable type 060 * @return a new {@link RepeatableContainers} instance 061 */ 062 public RepeatableContainers and(Class<? extends Annotation> container, 063 Class<? extends Annotation> repeatable) { 064 065 return new ExplicitRepeatableContainer(this, repeatable, container); 066 } 067 068 @Nullable 069 Annotation[] findRepeatedAnnotations(Annotation annotation) { 070 if (this.parent == null) { 071 return null; 072 } 073 return this.parent.findRepeatedAnnotations(annotation); 074 } 075 076 077 @Override 078 public boolean equals(@Nullable Object other) { 079 if (other == this) { 080 return true; 081 } 082 if (other == null || getClass() != other.getClass()) { 083 return false; 084 } 085 return ObjectUtils.nullSafeEquals(this.parent, ((RepeatableContainers) other).parent); 086 } 087 088 @Override 089 public int hashCode() { 090 return ObjectUtils.nullSafeHashCode(this.parent); 091 } 092 093 094 /** 095 * Create a {@link RepeatableContainers} instance that searches using Java's 096 * {@link Repeatable @Repeatable} annotation. 097 * @return a {@link RepeatableContainers} instance 098 */ 099 public static RepeatableContainers standardRepeatables() { 100 return StandardRepeatableContainers.INSTANCE; 101 } 102 103 /** 104 * Create a {@link RepeatableContainers} instance that uses a defined 105 * container and repeatable type. 106 * @param repeatable the contained repeatable annotation 107 * @param container the container annotation or {@code null}. If specified, 108 * this annotation must declare a {@code value} attribute returning an array 109 * of repeatable annotations. If not specified, the container will be 110 * deduced by inspecting the {@code @Repeatable} annotation on 111 * {@code repeatable}. 112 * @return a {@link RepeatableContainers} instance 113 */ 114 public static RepeatableContainers of( 115 Class<? extends Annotation> repeatable, @Nullable Class<? extends Annotation> container) { 116 117 return new ExplicitRepeatableContainer(null, repeatable, container); 118 } 119 120 /** 121 * Create a {@link RepeatableContainers} instance that does not expand any 122 * repeatable annotations. 123 * @return a {@link RepeatableContainers} instance 124 */ 125 public static RepeatableContainers none() { 126 return NoRepeatableContainers.INSTANCE; 127 } 128 129 130 /** 131 * Standard {@link RepeatableContainers} implementation that searches using 132 * Java's {@link Repeatable @Repeatable} annotation. 133 */ 134 private static class StandardRepeatableContainers extends RepeatableContainers { 135 136 private static final Map<Class<? extends Annotation>, Object> cache = new ConcurrentReferenceHashMap<>(); 137 138 private static final Object NONE = new Object(); 139 140 private static StandardRepeatableContainers INSTANCE = new StandardRepeatableContainers(); 141 142 StandardRepeatableContainers() { 143 super(null); 144 } 145 146 @Override 147 @Nullable 148 Annotation[] findRepeatedAnnotations(Annotation annotation) { 149 Method method = getRepeatedAnnotationsMethod(annotation.annotationType()); 150 if (method != null) { 151 return (Annotation[]) ReflectionUtils.invokeMethod(method, annotation); 152 } 153 return super.findRepeatedAnnotations(annotation); 154 } 155 156 @Nullable 157 private static Method getRepeatedAnnotationsMethod(Class<? extends Annotation> annotationType) { 158 Object result = cache.computeIfAbsent(annotationType, 159 StandardRepeatableContainers::computeRepeatedAnnotationsMethod); 160 return (result != NONE ? (Method) result : null); 161 } 162 163 private static Object computeRepeatedAnnotationsMethod(Class<? extends Annotation> annotationType) { 164 AttributeMethods methods = AttributeMethods.forAnnotationType(annotationType); 165 if (methods.hasOnlyValueAttribute()) { 166 Method method = methods.get(0); 167 Class<?> returnType = method.getReturnType(); 168 if (returnType.isArray()) { 169 Class<?> componentType = returnType.getComponentType(); 170 if (Annotation.class.isAssignableFrom(componentType) && 171 componentType.isAnnotationPresent(Repeatable.class)) { 172 return method; 173 } 174 } 175 } 176 return NONE; 177 } 178 } 179 180 181 /** 182 * A single explicit mapping. 183 */ 184 private static class ExplicitRepeatableContainer extends RepeatableContainers { 185 186 private final Class<? extends Annotation> repeatable; 187 188 private final Class<? extends Annotation> container; 189 190 private final Method valueMethod; 191 192 ExplicitRepeatableContainer(@Nullable RepeatableContainers parent, 193 Class<? extends Annotation> repeatable, @Nullable Class<? extends Annotation> container) { 194 195 super(parent); 196 Assert.notNull(repeatable, "Repeatable must not be null"); 197 if (container == null) { 198 container = deduceContainer(repeatable); 199 } 200 Method valueMethod = AttributeMethods.forAnnotationType(container).get(MergedAnnotation.VALUE); 201 try { 202 if (valueMethod == null) { 203 throw new NoSuchMethodException("No value method found"); 204 } 205 Class<?> returnType = valueMethod.getReturnType(); 206 if (!returnType.isArray() || returnType.getComponentType() != repeatable) { 207 throw new AnnotationConfigurationException("Container type [" + 208 container.getName() + 209 "] must declare a 'value' attribute for an array of type [" + 210 repeatable.getName() + "]"); 211 } 212 } 213 catch (AnnotationConfigurationException ex) { 214 throw ex; 215 } 216 catch (Throwable ex) { 217 throw new AnnotationConfigurationException( 218 "Invalid declaration of container type [" + container.getName() + 219 "] for repeatable annotation [" + repeatable.getName() + "]", 220 ex); 221 } 222 this.repeatable = repeatable; 223 this.container = container; 224 this.valueMethod = valueMethod; 225 } 226 227 private Class<? extends Annotation> deduceContainer(Class<? extends Annotation> repeatable) { 228 Repeatable annotation = repeatable.getAnnotation(Repeatable.class); 229 Assert.notNull(annotation, () -> "Annotation type must be a repeatable annotation: " + 230 "failed to resolve container type for " + repeatable.getName()); 231 return annotation.value(); 232 } 233 234 @Override 235 @Nullable 236 Annotation[] findRepeatedAnnotations(Annotation annotation) { 237 if (this.container.isAssignableFrom(annotation.annotationType())) { 238 return (Annotation[]) ReflectionUtils.invokeMethod(this.valueMethod, annotation); 239 } 240 return super.findRepeatedAnnotations(annotation); 241 } 242 243 @Override 244 public boolean equals(@Nullable Object other) { 245 if (!super.equals(other)) { 246 return false; 247 } 248 ExplicitRepeatableContainer otherErc = (ExplicitRepeatableContainer) other; 249 return (this.container.equals(otherErc.container) && this.repeatable.equals(otherErc.repeatable)); 250 } 251 252 @Override 253 public int hashCode() { 254 int hashCode = super.hashCode(); 255 hashCode = 31 * hashCode + this.container.hashCode(); 256 hashCode = 31 * hashCode + this.repeatable.hashCode(); 257 return hashCode; 258 } 259 } 260 261 262 /** 263 * No repeatable containers. 264 */ 265 private static class NoRepeatableContainers extends RepeatableContainers { 266 267 private static NoRepeatableContainers INSTANCE = new NoRepeatableContainers(); 268 269 NoRepeatableContainers() { 270 super(null); 271 } 272 } 273 274}