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