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      processingContext.logWarn("there are " + remaining + " beans with unsatisfied dependencies (assuming external dependencies)");
120      ordering.warnOnDependencies();
121    }
122
123    try {
124      SimpleFactoryWriter factoryWriter = new SimpleFactoryWriter(ordering, processingContext);
125      factoryWriter.write();
126    } catch (FilerException e) {
127      processingContext.logWarn("FilerException trying to write factory " + e.getMessage());
128    } catch (IOException e) {
129      processingContext.logError("Failed to write factory " + e.getMessage());
130    }
131  }
132
133  /**
134   * Read the beans that have changed.
135   */
136  private void readChangedBeans(Set<? extends Element> beans, boolean factory) {
137    for (Element element : beans) {
138      if (!(element instanceof TypeElement)) {
139        processingContext.logError("unexpected type [" + element + "]");
140      } else {
141        if (readBeans.add(element.toString())) {
142          readBeanMeta((TypeElement) element, factory);
143        } else {
144          processingContext.logDebug("skipping already processed bean " + element);
145        }
146      }
147    }
148  }
149
150  /**
151   * Merge the changed bean meta data into the existing (factory) metaData.
152   */
153  private void mergeMetaData() {
154    for (BeanReader beanReader : beanReaders) {
155      if (beanReader.isRequestScoped()) {
156        processingContext.logWarn("skipping request scoped processed bean " + beanReader);
157      } else {
158        String metaKey = beanReader.getMetaKey();
159        MetaData metaData = this.metaData.get(metaKey);
160        if (metaData == null) {
161          addMeta(beanReader);
162        } else {
163          updateMeta(metaData, beanReader);
164        }
165      }
166    }
167  }
168
169  /**
170   * Add a new previously unknown bean.
171   */
172  private void addMeta(BeanReader beanReader) {
173    MetaData meta = beanReader.createMeta();
174    metaData.put(meta.getKey(), meta);
175    for (MetaData methodMeta : beanReader.createFactoryMethodMeta()) {
176      metaData.put(methodMeta.getKey(), methodMeta);
177    }
178  }
179
180  /**
181   * Update the meta data on a previously known bean.
182   */
183  private void updateMeta(MetaData metaData, BeanReader beanReader) {
184    metaData.update(beanReader);
185  }
186
187  /**
188   * Read the dependency injection meta data for the given bean.
189   */
190  private void readBeanMeta(TypeElement typeElement, boolean factory) {
191
192    if (typeElement.getKind() == ElementKind.ANNOTATION_TYPE) {
193      processingContext.logWarn("skipping annotation type " + typeElement);
194      return;
195    }
196    BeanReader beanReader = new BeanReader(typeElement, processingContext);
197    beanReader.read(factory);
198    beanReaders.add(beanReader);
199  }
200
201  /**
202   * Read the existing meta data from ContextModule (if found) and the factory bean (if exists).
203   */
204  private void readModule(RoundEnvironment roundEnv) {
205
206    String factory = processingContext.loadMetaInfServices();
207    if (factory != null) {
208      TypeElement factoryType = elementUtils.getTypeElement(factory);
209      if (factoryType != null) {
210        readFactory(factoryType);
211      }
212    }
213
214    Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(ContextModule.class);
215    if (!elementsAnnotatedWith.isEmpty()) {
216      Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator();
217      if (iterator.hasNext()) {
218        Element element = iterator.next();
219        ContextModule annotation = element.getAnnotation(ContextModule.class);
220        if (annotation != null) {
221          processingContext.setContextDetails(annotation.name(), annotation.provides(), annotation.dependsOn(), element);
222        }
223      }
224    }
225  }
226
227
228  /**
229   * Read the existing factory bean. Each of the build methods is annotated with <code>@DependencyMeta</code>
230   * which holds the information we need (to regenerate the factory with any changes).
231   */
232  private void readFactory(TypeElement factoryType) {
233
234    ContextModule module = factoryType.getAnnotation(ContextModule.class);
235    processingContext.setContextDetails(module.name(), module.provides(), module.dependsOn(), factoryType);
236
237    List<? extends Element> elements = factoryType.getEnclosedElements();
238    if (elements != null) {
239      for (Element element : elements) {
240        ElementKind kind = element.getKind();
241        if (ElementKind.METHOD == kind) {
242
243          Name simpleName = element.getSimpleName();
244          if (simpleName.toString().startsWith("build")) {
245            // read a build method - DependencyMeta
246            DependencyMeta meta = element.getAnnotation(DependencyMeta.class);
247            if (meta == null) {
248              processingContext.logError("Missing @DependencyMeta on method " + simpleName.toString());
249            } else {
250              final MetaData metaData = new MetaData(meta);
251              this.metaData.put(metaData.getKey(), metaData);
252            }
253          }
254        }
255      }
256    }
257  }
258}