001/*
002 * Copyright 2002-2020 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.ArrayList;
021import java.util.Collection;
022import java.util.LinkedHashSet;
023import java.util.Set;
024import java.util.function.Function;
025import java.util.function.IntFunction;
026import java.util.stream.Collector;
027import java.util.stream.Collector.Characteristics;
028
029import org.springframework.core.annotation.MergedAnnotation.Adapt;
030import org.springframework.util.LinkedMultiValueMap;
031import org.springframework.util.MultiValueMap;
032
033/**
034 * {@link Collector} implementations that provide various reduction operations for
035 * {@link MergedAnnotation} instances.
036 *
037 * @author Phillip Webb
038 * @author Sam Brannen
039 * @since 5.2
040 */
041public abstract class MergedAnnotationCollectors {
042
043        private static final Characteristics[] NO_CHARACTERISTICS = {};
044
045        private static final Characteristics[] IDENTITY_FINISH_CHARACTERISTICS = {Characteristics.IDENTITY_FINISH};
046
047
048        private MergedAnnotationCollectors() {
049        }
050
051
052        /**
053         * Create a new {@link Collector} that accumulates merged annotations to a
054         * {@link LinkedHashSet} containing {@linkplain MergedAnnotation#synthesize()
055         * synthesized} versions.
056         * <p>The collector returned by this method is effectively equivalent to
057         * {@code Collectors.mapping(MergedAnnotation::synthesize, Collectors.toCollection(LinkedHashSet::new))}
058         * but avoids the creation of a composite collector.
059         * @param <A> the annotation type
060         * @return a {@link Collector} which collects and synthesizes the
061         * annotations into a {@link Set}
062         */
063        public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, Set<A>> toAnnotationSet() {
064                return Collector.of(LinkedHashSet::new, (set, annotation) -> set.add(annotation.synthesize()),
065                                MergedAnnotationCollectors::combiner);
066        }
067
068        /**
069         * Create a new {@link Collector} that accumulates merged annotations to an
070         * {@link Annotation} array containing {@linkplain MergedAnnotation#synthesize()
071         * synthesized} versions.
072         * @param <A> the annotation type
073         * @return a {@link Collector} which collects and synthesizes the
074         * annotations into an {@code Annotation[]}
075         * @see #toAnnotationArray(IntFunction)
076         */
077        public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, Annotation[]> toAnnotationArray() {
078                return toAnnotationArray(Annotation[]::new);
079        }
080
081        /**
082         * Create a new {@link Collector} that accumulates merged annotations to an
083         * {@link Annotation} array containing {@linkplain MergedAnnotation#synthesize()
084         * synthesized} versions.
085         * @param <A> the annotation type
086         * @param <R> the resulting array type
087         * @param generator a function which produces a new array of the desired
088         * type and the provided length
089         * @return a {@link Collector} which collects and synthesizes the
090         * annotations into an annotation array
091         * @see #toAnnotationArray
092         */
093        public static <R extends Annotation, A extends R> Collector<MergedAnnotation<A>, ?, R[]> toAnnotationArray(
094                        IntFunction<R[]> generator) {
095
096                return Collector.of(ArrayList::new, (list, annotation) -> list.add(annotation.synthesize()),
097                                MergedAnnotationCollectors::combiner, list -> list.toArray(generator.apply(list.size())));
098        }
099
100        /**
101         * Create a new {@link Collector} that accumulates merged annotations to a
102         * {@link MultiValueMap} with items {@linkplain MultiValueMap#add(Object, Object)
103         * added} from each merged annotation
104         * {@linkplain MergedAnnotation#asMap(Adapt...) as a map}.
105         * @param <A> the annotation type
106         * @param adaptations the adaptations that should be applied to the annotation values
107         * @return a {@link Collector} which collects and synthesizes the
108         * annotations into a {@link LinkedMultiValueMap}
109         * @see #toMultiValueMap(Function, MergedAnnotation.Adapt...)
110         */
111        public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, MultiValueMap<String, Object>> toMultiValueMap(
112                        Adapt... adaptations) {
113
114                return toMultiValueMap(Function.identity(), adaptations);
115        }
116
117        /**
118         * Create a new {@link Collector} that accumulates merged annotations to a
119         * {@link MultiValueMap} with items {@linkplain MultiValueMap#add(Object, Object)
120         * added} from each merged annotation
121         * {@linkplain MergedAnnotation#asMap(Adapt...) as a map}.
122         * @param <A> the annotation type
123         * @param finisher the finisher function for the new {@link MultiValueMap}
124         * @param adaptations the adaptations that should be applied to the annotation values
125         * @return a {@link Collector} which collects and synthesizes the
126         * annotations into a {@link LinkedMultiValueMap}
127         * @see #toMultiValueMap(MergedAnnotation.Adapt...)
128         */
129        public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, MultiValueMap<String, Object>> toMultiValueMap(
130                        Function<MultiValueMap<String, Object>, MultiValueMap<String, Object>> finisher,
131                        Adapt... adaptations) {
132
133                Characteristics[] characteristics = (isSameInstance(finisher, Function.identity()) ?
134                                IDENTITY_FINISH_CHARACTERISTICS : NO_CHARACTERISTICS);
135                return Collector.of(LinkedMultiValueMap::new,
136                                (map, annotation) -> annotation.asMap(adaptations).forEach(map::add),
137                                MergedAnnotationCollectors::combiner, finisher, characteristics);
138        }
139
140
141        private static boolean isSameInstance(Object instance, Object candidate) {
142                return instance == candidate;
143        }
144
145        /**
146         * {@link Collector#combiner() Combiner} for collections.
147         * <p>This method is only invoked if the {@link java.util.stream.Stream} is
148         * processed in {@linkplain java.util.stream.Stream#parallel() parallel}.
149         */
150        private static <E, C extends Collection<E>> C combiner(C collection, C additions) {
151                collection.addAll(additions);
152                return collection;
153        }
154
155        /**
156         * {@link Collector#combiner() Combiner} for multi-value maps.
157         * <p>This method is only invoked if the {@link java.util.stream.Stream} is
158         * processed in {@linkplain java.util.stream.Stream#parallel() parallel}.
159         */
160        private static <K, V> MultiValueMap<K, V> combiner(MultiValueMap<K, V> map, MultiValueMap<K, V> additions) {
161                map.addAll(additions);
162                return map;
163        }
164
165}