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