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