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