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}