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}