001package io.avaje.inject.generator;
002
003import io.avaje.inject.Factory;
004import io.avaje.inject.InjectModule;
005import io.avaje.inject.spi.Proxy;
006import javax.inject.Scope;
007import javax.inject.Singleton;
008
009import javax.annotation.processing.AbstractProcessor;
010import javax.annotation.processing.ProcessingEnvironment;
011import javax.annotation.processing.RoundEnvironment;
012import javax.lang.model.SourceVersion;
013import javax.lang.model.element.*;
014import javax.lang.model.util.Elements;
015import java.util.*;
016
017public class Processor extends AbstractProcessor {
018
019  private ProcessingContext context;
020  private Elements elementUtils;
021  private ScopeInfo defaultScope;
022  private AllScopes allScopes;
023  private boolean readModuleInfo;
024
025  public Processor() {
026  }
027
028  @Override
029  public SourceVersion getSupportedSourceVersion() {
030    return SourceVersion.latest();
031  }
032
033  @Override
034  public synchronized void init(ProcessingEnvironment processingEnv) {
035    super.init(processingEnv);
036    this.context = new ProcessingContext(processingEnv);
037    this.elementUtils = processingEnv.getElementUtils();
038    this.allScopes = new AllScopes(context);
039    this.defaultScope = allScopes.defaultScope();
040  }
041
042  @Override
043  public Set<String> getSupportedAnnotationTypes() {
044    Set<String> annotations = new LinkedHashSet<>();
045    annotations.add(InjectModule.class.getCanonicalName());
046    annotations.add(Factory.class.getCanonicalName());
047    annotations.add(Singleton.class.getCanonicalName());
048    annotations.add(Scope.class.getCanonicalName());
049    annotations.add(Constants.TESTSCOPE);
050    annotations.add(Constants.CONTROLLER);
051    return annotations;
052  }
053
054  @Override
055  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
056    Set<? extends Element> controllers = Collections.emptySet();
057    TypeElement typeElement = elementUtils.getTypeElement(Constants.CONTROLLER);
058    if (typeElement != null) {
059      controllers = roundEnv.getElementsAnnotatedWith(typeElement);
060    }
061
062    Set<? extends Element> factoryBeans = roundEnv.getElementsAnnotatedWith(Factory.class);
063    Set<? extends Element> beans = roundEnv.getElementsAnnotatedWith(Singleton.class);
064    Set<? extends Element> scopes = roundEnv.getElementsAnnotatedWith(Scope.class);
065    Set<? extends Element> proxies = roundEnv.getElementsAnnotatedWith(Proxy.class);
066    readScopes(scopes);
067    readModule(roundEnv);
068    readChangedBeans(factoryBeans, true);
069    readChangedBeans(beans, false);
070    readChangedBeans(controllers, false);
071    readChangedBeans(proxies, false);
072    allScopes.readBeans(roundEnv);
073    defaultScope.write(roundEnv.processingOver());
074    allScopes.write(roundEnv.processingOver());
075    return false;
076  }
077
078  private void readScopes(Set<? extends Element> scopes) {
079    for (Element element : scopes) {
080      if (element.getKind() == ElementKind.ANNOTATION_TYPE) {
081        // context.logDebug("detected scope annotation " + element);
082        TypeElement type = (TypeElement) element;
083        allScopes.addScopeAnnotation(type);
084      }
085    }
086    addTestScope();
087  }
088
089  /**
090   * Add built-in test scope for <code>@TestScope</code> if available.
091   */
092  private void addTestScope() {
093    TypeElement testScopeType = elementUtils.getTypeElement(Constants.TESTSCOPE);
094    if (testScopeType != null) {
095      allScopes.addScopeAnnotation(testScopeType);
096    }
097  }
098
099  /**
100   * Read the beans that have changed.
101   */
102  private void readChangedBeans(Set<? extends Element> beans, boolean factory) {
103    for (Element element : beans) {
104      if (!(element instanceof TypeElement)) {
105        context.logError("unexpected type [" + element + "]");
106      } else {
107        TypeElement typeElement = (TypeElement) element;
108        final ScopeInfo scope = findScope(typeElement);
109        if (!factory) {
110          // will be found via custom scope so effectively ignore additional @Singleton
111          if (scope == null) {
112            defaultScope.read(typeElement, false);
113          }
114        } else {
115          if (scope != null) {
116            // context.logWarn("Adding factory to custom scope "+element+" scope: "+scope);
117            scope.read(typeElement, true);
118          } else {
119            defaultScope.read(typeElement, true);
120          }
121        }
122      }
123    }
124  }
125
126  /**
127   * Find the scope if the Factory has a scope annotation.
128   */
129  private ScopeInfo findScope(Element element) {
130    for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
131      final ScopeInfo scopeInfo = allScopes.get(annotationMirror.getAnnotationType().toString());
132      if (scopeInfo != null) {
133        return scopeInfo;
134      }
135    }
136    return null;
137  }
138
139  /**
140   * Read the existing meta data from InjectModule (if found) and the factory bean (if exists).
141   */
142  private void readModule(RoundEnvironment roundEnv) {
143    if (readModuleInfo) {
144      // only read the module meta data once
145      return;
146    }
147    readModuleInfo = true;
148    String factory = context.loadMetaInfServices();
149    if (factory != null) {
150      TypeElement moduleType = elementUtils.getTypeElement(factory);
151      if (moduleType != null) {
152        defaultScope.readModuleMetaData(moduleType);
153      }
154    }
155    allScopes.readModules(context.loadMetaInfCustom());
156    readInjectModule(roundEnv);
157  }
158
159  /**
160   * Read InjectModule for things like package-info etc (not for custom scopes)
161   */
162  private void readInjectModule(RoundEnvironment roundEnv) {
163    // read other that are annotated with InjectModule
164    Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(InjectModule.class);
165    if (!elementsAnnotatedWith.isEmpty()) {
166      Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator();
167      if (iterator.hasNext()) {
168        Element element = iterator.next();
169        Scope scope = element.getAnnotation(Scope.class);
170        if (scope == null) {
171          // it it not a custom scope annotation
172          InjectModule annotation = element.getAnnotation(InjectModule.class);
173          if (annotation != null) {
174            defaultScope.details(annotation.name(), element);
175          }
176        }
177      }
178    }
179  }
180
181}