001/*
002 * Copyright 2002-2018 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.context.index.processor;
018
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.EnumSet;
023import java.util.LinkedHashSet;
024import java.util.List;
025import java.util.Set;
026
027import javax.annotation.processing.Completion;
028import javax.annotation.processing.ProcessingEnvironment;
029import javax.annotation.processing.Processor;
030import javax.annotation.processing.RoundEnvironment;
031import javax.lang.model.SourceVersion;
032import javax.lang.model.element.AnnotationMirror;
033import javax.lang.model.element.Element;
034import javax.lang.model.element.ElementKind;
035import javax.lang.model.element.ExecutableElement;
036import javax.lang.model.element.Modifier;
037import javax.lang.model.element.TypeElement;
038
039/**
040 * Annotation {@link Processor} that writes {@link CandidateComponentsMetadata}
041 * file for spring components.
042 *
043 * @author Stephane Nicoll
044 * @author Juergen Hoeller
045 * @since 5.0
046 */
047public class CandidateComponentsIndexer implements Processor {
048
049        private static final Set<ElementKind> TYPE_KINDS =
050                        Collections.unmodifiableSet(EnumSet.of(ElementKind.CLASS, ElementKind.INTERFACE));
051
052        private MetadataStore metadataStore;
053
054        private MetadataCollector metadataCollector;
055
056        private TypeHelper typeHelper;
057
058        private List<StereotypesProvider> stereotypesProviders;
059
060
061        @Override
062        public Set<String> getSupportedOptions() {
063                return Collections.emptySet();
064        }
065
066        @Override
067        public Set<String> getSupportedAnnotationTypes() {
068                return Collections.singleton("*");
069        }
070
071        @Override
072        public SourceVersion getSupportedSourceVersion() {
073                return SourceVersion.latest();
074        }
075
076        @Override
077        public synchronized void init(ProcessingEnvironment env) {
078                this.stereotypesProviders = getStereotypesProviders(env);
079                this.typeHelper = new TypeHelper(env);
080                this.metadataStore = new MetadataStore(env);
081                this.metadataCollector = new MetadataCollector(env, this.metadataStore.readMetadata());
082        }
083
084        @Override
085        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
086                this.metadataCollector.processing(roundEnv);
087                roundEnv.getRootElements().forEach(this::processElement);
088                if (roundEnv.processingOver()) {
089                        writeMetaData();
090                }
091                return false;
092        }
093
094        @Override
095        public Iterable<? extends Completion> getCompletions(
096                        Element element, AnnotationMirror annotation, ExecutableElement member, String userText) {
097
098                return Collections.emptyList();
099        }
100
101
102        private List<StereotypesProvider> getStereotypesProviders(ProcessingEnvironment env) {
103                List<StereotypesProvider> result = new ArrayList<>();
104                TypeHelper typeHelper = new TypeHelper(env);
105                result.add(new IndexedStereotypesProvider(typeHelper));
106                result.add(new StandardStereotypesProvider(typeHelper));
107                result.add(new PackageInfoStereotypesProvider());
108                return result;
109        }
110
111        private void processElement(Element element) {
112                addMetadataFor(element);
113                staticTypesIn(element.getEnclosedElements()).forEach(this::processElement);
114        }
115
116        private void addMetadataFor(Element element) {
117                Set<String> stereotypes = new LinkedHashSet<>();
118                this.stereotypesProviders.forEach(p -> stereotypes.addAll(p.getStereotypes(element)));
119                if (!stereotypes.isEmpty()) {
120                        this.metadataCollector.add(new ItemMetadata(this.typeHelper.getType(element), stereotypes));
121                }
122        }
123
124        private void writeMetaData() {
125                CandidateComponentsMetadata metadata = this.metadataCollector.getMetadata();
126                if (!metadata.getItems().isEmpty()) {
127                        try {
128                                this.metadataStore.writeMetadata(metadata);
129                        }
130                        catch (IOException ex) {
131                                throw new IllegalStateException("Failed to write metadata", ex);
132                        }
133                }
134        }
135
136        private static List<TypeElement> staticTypesIn(Iterable<? extends Element> elements) {
137                List<TypeElement> list = new ArrayList<>();
138                for (Element element : elements) {
139                        if (TYPE_KINDS.contains(element.getKind()) && element.getModifiers().contains(Modifier.STATIC)) {
140                                list.add(TypeElement.class.cast(element));
141                        }
142                }
143                return list;
144        }
145
146}