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.util.Collection;
021import java.util.HashSet;
022import java.util.Set;
023import java.util.function.Function;
024import java.util.function.Predicate;
025
026import org.springframework.lang.Nullable;
027import org.springframework.util.Assert;
028import org.springframework.util.ObjectUtils;
029
030/**
031 * Predicate implementations that provide various test operations for
032 * {@link MergedAnnotation MergedAnnotations}.
033 *
034 * @author Phillip Webb
035 * @since 5.2
036 */
037public abstract class MergedAnnotationPredicates {
038
039        private MergedAnnotationPredicates() {
040        }
041
042
043        /**
044         * Create a new {@link Predicate} that evaluates to {@code true} if the name of the
045         * {@linkplain MergedAnnotation#getType() merged annotation type} is contained in
046         * the specified array.
047         * @param <A> the annotation type
048         * @param typeNames the names that should be matched
049         * @return a {@link Predicate} to test the annotation type
050         */
051        public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(String... typeNames) {
052                return annotation -> ObjectUtils.containsElement(typeNames, annotation.getType().getName());
053        }
054
055        /**
056         * Create a new {@link Predicate} that evaluates to {@code true} if the
057         * {@linkplain MergedAnnotation#getType() merged annotation type} is contained in
058         * the specified array.
059         * @param <A> the annotation type
060         * @param types the types that should be matched
061         * @return a {@link Predicate} to test the annotation type
062         */
063        public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(Class<?>... types) {
064                return annotation -> ObjectUtils.containsElement(types, annotation.getType());
065        }
066
067        /**
068         * Create a new {@link Predicate} that evaluates to {@code true} if the
069         * {@linkplain MergedAnnotation#getType() merged annotation type} is contained in
070         * the specified collection.
071         * @param <A> the annotation type
072         * @param types the type names or classes that should be matched
073         * @return a {@link Predicate} to test the annotation type
074         */
075        public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(Collection<?> types) {
076                return annotation -> types.stream()
077                                .map(type -> type instanceof Class ? ((Class<?>) type).getName() : type.toString())
078                                .anyMatch(typeName -> typeName.equals(annotation.getType().getName()));
079        }
080
081        /**
082         * Create a new stateful, single use {@link Predicate} that matches only
083         * the first run of an extracted value. For example,
084         * {@code MergedAnnotationPredicates.firstRunOf(MergedAnnotation::distance)}
085         * will match the first annotation, and any subsequent runs that have the
086         * same distance.
087         * <p>NOTE: This predicate only matches the first run. Once the extracted
088         * value changes, the predicate always returns {@code false}. For example,
089         * if you have a set of annotations with distances {@code [1, 1, 2, 1]} then
090         * only the first two will match.
091         * @param valueExtractor function used to extract the value to check
092         * @return a {@link Predicate} that matches the first run of the extracted
093         * values
094         */
095        public static <A extends Annotation> Predicate<MergedAnnotation<A>> firstRunOf(
096                        Function<? super MergedAnnotation<A>, ?> valueExtractor) {
097
098                return new FirstRunOfPredicate<>(valueExtractor);
099        }
100
101        /**
102         * Create a new stateful, single use {@link Predicate} that matches
103         * annotations that are unique based on the extracted key. For example
104         * {@code MergedAnnotationPredicates.unique(MergedAnnotation::getType)} will
105         * match the first time a unique type is encountered.
106         * @param keyExtractor function used to extract the key used to test for
107         * uniqueness
108         * @return a {@link Predicate} that matches a unique annotation based on the
109         * extracted key
110         */
111        public static <A extends Annotation, K> Predicate<MergedAnnotation<A>> unique(
112                        Function<? super MergedAnnotation<A>, K> keyExtractor) {
113
114                return new UniquePredicate<>(keyExtractor);
115        }
116
117
118        /**
119         * {@link Predicate} implementation used for
120         * {@link MergedAnnotationPredicates#firstRunOf(Function)}.
121         */
122        private static class FirstRunOfPredicate<A extends Annotation> implements Predicate<MergedAnnotation<A>> {
123
124                private final Function<? super MergedAnnotation<A>, ?> valueExtractor;
125
126                private boolean hasLastValue;
127
128                @Nullable
129                private Object lastValue;
130
131                FirstRunOfPredicate(Function<? super MergedAnnotation<A>, ?> valueExtractor) {
132                        Assert.notNull(valueExtractor, "Value extractor must not be null");
133                        this.valueExtractor = valueExtractor;
134                }
135
136                @Override
137                public boolean test(@Nullable MergedAnnotation<A> annotation) {
138                        if (!this.hasLastValue) {
139                                this.hasLastValue = true;
140                                this.lastValue = this.valueExtractor.apply(annotation);
141                        }
142                        Object value = this.valueExtractor.apply(annotation);
143                        return ObjectUtils.nullSafeEquals(value, this.lastValue);
144
145                }
146        }
147
148
149        /**
150         * {@link Predicate} implementation used for
151         * {@link MergedAnnotationPredicates#unique(Function)}.
152         */
153        private static class UniquePredicate<A extends Annotation, K> implements Predicate<MergedAnnotation<A>> {
154
155                private final Function<? super MergedAnnotation<A>, K> keyExtractor;
156
157                private final Set<K> seen = new HashSet<>();
158
159                UniquePredicate(Function<? super MergedAnnotation<A>, K> keyExtractor) {
160                        Assert.notNull(keyExtractor, "Key extractor must not be null");
161                        this.keyExtractor = keyExtractor;
162                }
163
164                @Override
165                public boolean test(@Nullable MergedAnnotation<A> annotation) {
166                        K key = this.keyExtractor.apply(annotation);
167                        return this.seen.add(key);
168                }
169        }
170
171}