dagger.internal.codegen.ModuleAdapterProcessor Maven / Gradle / Ivy
                 Go to download
                
        
                    Show more of this group  Show more artifacts with this name
Show all versions of dagger-compiler Show documentation
                Show all versions of dagger-compiler Show documentation
    Tools to generate Dagger injection and module adapters from annotated code and validate them.
  
                
             The newest version!
        
        /*
 * Copyright (C) 2012 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dagger.internal.codegen;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
import dagger.internal.BindingsGroup;
import dagger.internal.Linker;
import dagger.internal.ModuleAdapter;
import dagger.internal.ProvidesBinding;
import dagger.internal.SetBinding;
import dagger.internal.codegen.Util.CodeGenerationIncompleteException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import static dagger.internal.codegen.AdapterJavadocs.bindingTypeDocs;
import static dagger.internal.codegen.Util.ARRAY_OF_CLASS;
import static dagger.internal.codegen.Util.bindingOf;
import static dagger.internal.codegen.Util.elementToString;
import static dagger.internal.codegen.Util.getAnnotation;
import static dagger.internal.codegen.Util.getNoArgsConstructor;
import static dagger.internal.codegen.Util.isCallableConstructor;
import static dagger.internal.codegen.Util.isInterface;
import static dagger.internal.codegen.Util.typeToString;
import static dagger.internal.loaders.GeneratedAdapters.MODULE_ADAPTER_SUFFIX;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
/**
 * Generates an implementation of {@link ModuleAdapter} that includes a binding
 * for each {@code @Provides} method of a target class.
 */
@SupportedAnnotationTypes({ "*" })
public final class ModuleAdapterProcessor extends AbstractProcessor {
  private static final List INVALID_RETURN_TYPES =
      Arrays.asList(Provider.class.getCanonicalName(), Lazy.class.getCanonicalName());
  private final LinkedHashMap> remainingTypes =
      new LinkedHashMap>();
  @Override public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
  }
  @Override public boolean process(Set extends TypeElement> types, RoundEnvironment env) {
    remainingTypes.putAll(providerMethodsByClass(env));
    for (Iterator i = remainingTypes.keySet().iterator(); i.hasNext();) {
      String typeName = i.next();
      TypeElement type = processingEnv.getElementUtils().getTypeElement(typeName);
      List providesTypes = remainingTypes.get(typeName);
      try {
        // Attempt to get the annotation. If types are missing, this will throw
        // CodeGenerationIncompleteException.
        Map parsedAnnotation = getAnnotation(Module.class, type);
        if (parsedAnnotation == null) {
          error(type + " has @Provides methods but no @Module annotation", type);
          continue;
        }
        JavaFile javaFile = generateModuleAdapter(type, parsedAnnotation, providesTypes);
        javaFile.writeTo(processingEnv.getFiler());
      } catch (CodeGenerationIncompleteException e) {
        continue; // A dependent type was not defined, we'll try to catch it on another pass.
      } catch (IOException e) {
        error("Code gen failed: " + e, type);
      }
      i.remove();
    }
    if (env.processingOver() && remainingTypes.size() > 0) {
      processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
          "Could not find types required by provides methods for " + remainingTypes.keySet());
    }
    return false; // FullGraphProcessor needs an opportunity to process.
  }
  private void error(String msg, Element element) {
    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, element);
  }
  /**
   * Returns a map containing all {@code @Provides} methods, indexed by class.
   */
  private Map> providerMethodsByClass(RoundEnvironment env) {
    Elements elementUtils = processingEnv.getElementUtils();
    Types types = processingEnv.getTypeUtils();
    Map> result = new HashMap>();
    provides:
    for (Element providerMethod : findProvidesMethods(env)) {
      switch (providerMethod.getEnclosingElement().getKind()) {
        case CLASS:
          break; // valid, move along
        default:
          // TODO(tbroyer): pass annotation information
          error("Unexpected @Provides on " + elementToString(providerMethod), providerMethod);
          continue;
      }
      TypeElement type = (TypeElement) providerMethod.getEnclosingElement();
      Set typeModifiers = type.getModifiers();
      if (typeModifiers.contains(PRIVATE)
          || typeModifiers.contains(ABSTRACT)) {
        error("Classes declaring @Provides methods must not be private or abstract: "
                + type.getQualifiedName(), type);
        continue;
      }
      Set methodModifiers = providerMethod.getModifiers();
      if (methodModifiers.contains(PRIVATE)
          || methodModifiers.contains(ABSTRACT)
          || methodModifiers.contains(STATIC)) {
        error("@Provides methods must not be private, abstract or static: "
                + type.getQualifiedName() + "." + providerMethod, providerMethod);
        continue;
      }
      ExecutableElement providerMethodAsExecutable = (ExecutableElement) providerMethod;
      if (!providerMethodAsExecutable.getThrownTypes().isEmpty()) {
        error("@Provides methods must not have a throws clause: "
            + type.getQualifiedName() + "." + providerMethod, providerMethod);
        continue;
      }
      // Invalidate return types.
      TypeMirror returnType = types.erasure(providerMethodAsExecutable.getReturnType());
      if (!returnType.getKind().equals(TypeKind.ERROR)) {
        // Validate if we have a type to validate (a type yet to be generated by other
        // processors is not "invalid" in this way, so ignore).
        for (String invalidTypeName : INVALID_RETURN_TYPES) {
          TypeElement invalidTypeElement = elementUtils.getTypeElement(invalidTypeName);
          if (invalidTypeElement != null && types.isSameType(returnType,
              types.erasure(invalidTypeElement.asType()))) {
            error(String.format("@Provides method must not return %s directly: %s.%s",
                invalidTypeElement, type.getQualifiedName(), providerMethod), providerMethod);
            continue provides; // Skip to next provides method.
          }
        }
      }
      List methods = result.get(type.getQualifiedName().toString());
      if (methods == null) {
        methods = new ArrayList();
        result.put(type.getQualifiedName().toString(), methods);
      }
      methods.add(providerMethodAsExecutable);
    }
    TypeMirror objectType = elementUtils.getTypeElement("java.lang.Object").asType();
    // Catch any stray modules without @Provides since their injectable types
    // should still be registered and a ModuleAdapter should still be written.
    for (Element module : env.getElementsAnnotatedWith(Module.class)) {
      if (!module.getKind().equals(ElementKind.CLASS)) {
        error("Modules must be classes: " + elementToString(module), module);
        continue;
      }
      TypeElement moduleType = (TypeElement) module;
      // Verify that all modules do not extend from non-Object types.
      if (!types.isSameType(moduleType.getSuperclass(), objectType)) {
        error("Modules must not extend from other classes: " + elementToString(module), module);
      }
      String moduleName = moduleType.getQualifiedName().toString();
      if (result.containsKey(moduleName)) continue;
      result.put(moduleName, new ArrayList());
    }
    return result;
  }
  private Set extends Element> findProvidesMethods(RoundEnvironment env) {
    Set result = new LinkedHashSet();
    result.addAll(env.getElementsAnnotatedWith(Provides.class));
    return result;
  }
  /**
   * Write a companion class for {@code type} that implements {@link
   * ModuleAdapter} to expose its provider methods.
   */
  private JavaFile generateModuleAdapter(TypeElement type,
      Map module, List providerMethods) {
    Object[] staticInjections = (Object[]) module.get("staticInjections");
    Object[] injects = (Object[]) module.get("injects");
    Object[] includes = (Object[]) module.get("includes");
    boolean overrides = (Boolean) module.get("overrides");
    boolean complete = (Boolean) module.get("complete");
    boolean library = (Boolean) module.get("library");
    List                      © 2015 - 2025 Weber Informatics LLC | Privacy Policy