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