001package io.avaje.inject.generator;
002
003import io.avaje.inject.ContextModule;
004import io.avaje.inject.Factory;
005import io.avaje.inject.spi.DependencyMeta;
006
007import javax.annotation.processing.AbstractProcessor;
008import javax.annotation.processing.FilerException;
009import javax.annotation.processing.ProcessingEnvironment;
010import javax.annotation.processing.RoundEnvironment;
011import javax.inject.Singleton;
012import javax.lang.model.SourceVersion;
013import javax.lang.model.element.Element;
014import javax.lang.model.element.ElementKind;
015import javax.lang.model.element.Name;
016import javax.lang.model.element.TypeElement;
017import javax.lang.model.util.Elements;
018import java.io.IOException;
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.HashSet;
022import java.util.Iterator;
023import java.util.LinkedHashMap;
024import java.util.LinkedHashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029public class Processor extends AbstractProcessor {
030
031  private ProcessingContext context;
032
033  private Elements elementUtils;
034
035  /**
036   * Map to merge the existing meta data with partially compiled code. Keyed by type and qualifier/name.
037   */
038  private final Map<String, MetaData> metaData = new LinkedHashMap<>();
039
040  private final List<BeanReader> beanReaders = new ArrayList<>();
041
042  private final Set<String> readBeans = new HashSet<>();
043
044  public Processor() {
045  }
046
047  @Override
048  public SourceVersion getSupportedSourceVersion() {
049    return SourceVersion.latest();
050  }
051
052  @Override
053  public synchronized void init(ProcessingEnvironment processingEnv) {
054    super.init(processingEnv);
055    this.context = new ProcessingContext(processingEnv);
056    this.elementUtils = processingEnv.getElementUtils();
057  }
058
059  @Override
060  public Set<String> getSupportedAnnotationTypes() {
061
062    Set<String> annotations = new LinkedHashSet<>();
063    annotations.add(ContextModule.class.getCanonicalName());
064    annotations.add(Factory.class.getCanonicalName());
065    annotations.add(Singleton.class.getCanonicalName());
066    annotations.add(Constants.CONTROLLER);
067    return annotations;
068  }
069
070  @Override
071  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
072
073    Set<? extends Element> controllers = Collections.emptySet();
074    TypeElement typeElement = elementUtils.getTypeElement(Constants.CONTROLLER);
075    if (typeElement != null) {
076      controllers = roundEnv.getElementsAnnotatedWith(typeElement);
077    }
078
079    Set<? extends Element> factoryBeans = roundEnv.getElementsAnnotatedWith(Factory.class);
080    Set<? extends Element> beans = roundEnv.getElementsAnnotatedWith(Singleton.class);
081
082    readModule(roundEnv);
083    readChangedBeans(factoryBeans, true);
084    readChangedBeans(beans, false);
085    readChangedBeans(controllers, false);
086
087    mergeMetaData();
088
089    writeBeanHelpers();
090    if (roundEnv.processingOver()) {
091      writeBeanFactory();
092    }
093
094    return false;
095  }
096
097  private void writeBeanHelpers() {
098    for (BeanReader beanReader : beanReaders) {
099      try {
100        if (!beanReader.isWrittenToFile()) {
101          SimpleBeanWriter writer = new SimpleBeanWriter(beanReader, context);
102          writer.write();
103          beanReader.setWrittenToFile();
104        }
105      } catch (FilerException e) {
106        context.logWarn("FilerException to write $di class " + beanReader.getBeanType() + " " + e.getMessage());
107
108      } catch (IOException e) {
109        e.printStackTrace();
110        context.logError(beanReader.getBeanType(), "Failed to write $di class");
111      }
112    }
113  }
114
115  private void writeBeanFactory() {
116
117    MetaDataOrdering ordering = new MetaDataOrdering(metaData.values(), context);
118    int remaining = ordering.processQueue();
119    if (remaining > 0) {
120      if (ordering.hasCircularDependencies()) {
121        ordering.errorOnCircularDependencies();
122      } else {
123        context.logWarn("there are " + remaining + " beans with unsatisfied dependencies (assuming external dependencies)");
124        ordering.warnOnDependencies();
125      }
126    }
127
128    try {
129      SimpleFactoryWriter factoryWriter = new SimpleFactoryWriter(ordering, context);
130      factoryWriter.write();
131    } catch (FilerException e) {
132      context.logWarn("FilerException trying to write factory " + e.getMessage());
133    } catch (IOException e) {
134      context.logError("Failed to write factory " + e.getMessage());
135    }
136  }
137
138  /**
139   * Read the beans that have changed.
140   */
141  private void readChangedBeans(Set<? extends Element> beans, boolean factory) {
142    for (Element element : beans) {
143      if (!(element instanceof TypeElement)) {
144        context.logError("unexpected type [" + element + "]");
145      } else {
146        if (readBeans.add(element.toString())) {
147          readBeanMeta((TypeElement) element, factory);
148        } else {
149          context.logDebug("skipping already processed bean " + element);
150        }
151      }
152    }
153  }
154
155  /**
156   * Merge the changed bean meta data into the existing (factory) metaData.
157   */
158  private void mergeMetaData() {
159    for (BeanReader beanReader : beanReaders) {
160      if (beanReader.isRequestScoped()) {
161        context.logDebug("skipping request scoped processed bean " + beanReader);
162      } else {
163        String metaKey = beanReader.getMetaKey();
164        MetaData metaData = this.metaData.get(metaKey);
165        if (metaData == null) {
166          addMeta(beanReader);
167        } else {
168          updateMeta(metaData, beanReader);
169        }
170      }
171    }
172  }
173
174  /**
175   * Add a new previously unknown bean.
176   */
177  private void addMeta(BeanReader beanReader) {
178    MetaData meta = beanReader.createMeta();
179    metaData.put(meta.getKey(), meta);
180    for (MetaData methodMeta : beanReader.createFactoryMethodMeta()) {
181      metaData.put(methodMeta.getKey(), methodMeta);
182    }
183  }
184
185  /**
186   * Update the meta data on a previously known bean.
187   */
188  private void updateMeta(MetaData metaData, BeanReader beanReader) {
189    metaData.update(beanReader);
190  }
191
192  /**
193   * Read the dependency injection meta data for the given bean.
194   */
195  private void readBeanMeta(TypeElement typeElement, boolean factory) {
196
197    if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) {
198      context.logDebug("skipping annotation type " + typeElement);
199      return;
200    }
201    BeanReader beanReader = new BeanReader(typeElement, context);
202    beanReader.read(factory);
203    beanReaders.add(beanReader);
204  }
205
206  /**
207   * Read the existing meta data from ContextModule (if found) and the factory bean (if exists).
208   */
209  private void readModule(RoundEnvironment roundEnv) {
210
211    String factory = context.loadMetaInfServices();
212    if (factory != null) {
213      TypeElement factoryType = elementUtils.getTypeElement(factory);
214      if (factoryType != null) {
215        readFactory(factoryType);
216      }
217    }
218
219    Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(ContextModule.class);
220    if (!elementsAnnotatedWith.isEmpty()) {
221      Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator();
222      if (iterator.hasNext()) {
223        Element element = iterator.next();
224        ContextModule annotation = element.getAnnotation(ContextModule.class);
225        if (annotation != null) {
226          context.setContextDetails(annotation.name(), annotation.provides(), annotation.dependsOn(), element);
227        }
228      }
229    }
230  }
231
232
233  /**
234   * Read the existing factory bean. Each of the build methods is annotated with <code>@DependencyMeta</code>
235   * which holds the information we need (to regenerate the factory with any changes).
236   */
237  private void readFactory(TypeElement factoryType) {
238
239    ContextModule module = factoryType.getAnnotation(ContextModule.class);
240    context.setContextDetails(module.name(), module.provides(), module.dependsOn(), factoryType);
241
242    List<? extends Element> elements = factoryType.getEnclosedElements();
243    if (elements != null) {
244      for (Element element : elements) {
245        ElementKind kind = element.getKind();
246        if (ElementKind.METHOD == kind) {
247
248          Name simpleName = element.getSimpleName();
249          if (simpleName.toString().startsWith("build")) {
250            // read a build method - DependencyMeta
251            DependencyMeta meta = element.getAnnotation(DependencyMeta.class);
252            if (meta == null) {
253              context.logError("Missing @DependencyMeta on method " + simpleName.toString());
254            } else {
255              final MetaData metaData = new MetaData(meta);
256              this.metaData.put(metaData.getKey(), metaData);
257            }
258          }
259        }
260      }
261    }
262  }
263}