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