Ejemplo n.º 1
0
  private void writeProvidesAdapter(
      JavaWriter writer,
      ExecutableElement providerMethod,
      Map<ExecutableElement, String> methodToClassName,
      Map<String, AtomicInteger> methodNameToNextId)
      throws IOException {
    String methodName = providerMethod.getSimpleName().toString();
    String moduleType = CodeGen.typeToString(providerMethod.getEnclosingElement().asType());
    String className = bindingClassName(providerMethod, methodToClassName, methodNameToNextId);
    String returnType = CodeGen.typeToString(providerMethod.getReturnType());
    List<? extends VariableElement> parameters = providerMethod.getParameters();
    boolean dependent = !parameters.isEmpty();

    writer.emitEmptyLine();
    writer.emitJavadoc(binderTypeDocs(returnType, false, false, dependent));
    writer.beginType(
        className,
        "class",
        PUBLIC | FINAL | STATIC,
        JavaWriter.type(Binding.class, returnType),
        JavaWriter.type(Provider.class, returnType));
    writer.emitField(moduleType, "module", PRIVATE | FINAL);
    for (Element parameter : parameters) {
      TypeMirror parameterType = parameter.asType();
      writer.emitField(
          JavaWriter.type(Binding.class, CodeGen.typeToString(parameterType)),
          parameterName(parameter),
          PRIVATE);
    }

    writer.emitEmptyLine();
    writer.beginMethod(null, className, PUBLIC, moduleType, "module");
    boolean singleton = providerMethod.getAnnotation(Singleton.class) != null;
    String key = JavaWriter.stringLiteral(GeneratorKeys.get(providerMethod));
    String membersKey = null;
    writer.emitStatement(
        "super(%s, %s, %s, %s.class)",
        key, membersKey, (singleton ? "IS_SINGLETON" : "NOT_SINGLETON"), moduleType);
    writer.emitStatement("this.module = module");
    writer.endMethod();

    if (dependent) {
      writer.emitEmptyLine();
      writer.emitJavadoc(ProcessorJavadocs.ATTACH_METHOD);
      writer.emitAnnotation(Override.class);
      writer.emitAnnotation(SuppressWarnings.class, JavaWriter.stringLiteral("unchecked"));
      writer.beginMethod("void", "attach", PUBLIC, Linker.class.getCanonicalName(), "linker");
      for (VariableElement parameter : parameters) {
        String parameterKey = GeneratorKeys.get(parameter);
        writer.emitStatement(
            "%s = (%s) linker.requestBinding(%s, %s.class)",
            parameterName(parameter),
            writer.compressType(
                JavaWriter.type(Binding.class, CodeGen.typeToString(parameter.asType()))),
            JavaWriter.stringLiteral(parameterKey),
            writer.compressType(moduleType));
      }
      writer.endMethod();

      writer.emitEmptyLine();
      writer.emitJavadoc(ProcessorJavadocs.GET_DEPENDENCIES_METHOD);
      writer.emitAnnotation(Override.class);
      String setOfBindings = JavaWriter.type(Set.class, "Binding<?>");
      writer.beginMethod(
          "void",
          "getDependencies",
          PUBLIC,
          setOfBindings,
          "getBindings",
          setOfBindings,
          "injectMembersBindings");
      for (Element parameter : parameters) {
        writer.emitStatement("getBindings.add(%s)", parameter.getSimpleName().toString());
      }
      writer.endMethod();
    }

    writer.emitEmptyLine();
    writer.emitJavadoc(ProcessorJavadocs.GET_METHOD, returnType);
    writer.emitAnnotation(Override.class);
    writer.beginMethod(returnType, "get", PUBLIC);
    StringBuilder args = new StringBuilder();
    boolean first = true;
    for (Element parameter : parameters) {
      if (!first) args.append(", ");
      else first = false;
      args.append(String.format("%s.get()", parameter.getSimpleName().toString()));
    }
    writer.emitStatement("return module.%s(%s)", methodName, args.toString());
    writer.endMethod();

    writer.endType();
  }
Ejemplo n.º 2
0
/**
 * Generates an implementation of {@link ModuleAdapter} that includes a binding for each
 * {@code @Provides} method of a target class.
 */
@SupportedAnnotationTypes({"dagger.Provides", "dagger.Module"})
public final class ProvidesProcessor extends AbstractProcessor {
  private final LinkedHashMap<String, List<ExecutableElement>> remainingTypes =
      new LinkedHashMap<String, List<ExecutableElement>>();
  private static final String BINDINGS_MAP =
      JavaWriter.type(
          Map.class, String.class.getCanonicalName(), Binding.class.getCanonicalName() + "<?>");

  @Override
  public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
  }

  @Override
  public boolean process(Set<? extends TypeElement> types, RoundEnvironment env) {
    remainingTypes.putAll(providerMethodsByClass(env));
    for (Iterator<String> i = remainingTypes.keySet().iterator(); i.hasNext(); ) {
      String typeName = i.next();
      TypeElement type = processingEnv.getElementUtils().getTypeElement(typeName);
      List<ExecutableElement> providesTypes = remainingTypes.get(typeName);
      try {
        // Attempt to get the annotation. If types are missing, this will throw
        // IllegalStateException.
        Map<String, Object> parsedAnnotation = CodeGen.getAnnotation(Module.class, type);
        try {
          writeModuleAdapter(type, parsedAnnotation, providesTypes);
        } catch (IOException e) {
          error("Code gen failed: " + e, type);
        }
        i.remove();
      } catch (IllegalStateException e) {
        // a dependent type was not defined, we'll catch it on another pass
      }
    }
    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<String, List<ExecutableElement>> providerMethodsByClass(RoundEnvironment env) {
    Map<String, List<ExecutableElement>> result = new HashMap<String, List<ExecutableElement>>();
    for (Element providerMethod : providesMethods(env)) {
      TypeElement type = (TypeElement) providerMethod.getEnclosingElement();
      Set<Modifier> typeModifiers = type.getModifiers();
      if (type.getKind() != ElementKind.CLASS) {
        // TODO(tbroyer): pass annotation information
        error("Unexpected @Provides on " + providerMethod, providerMethod);
        continue;
      }
      if (typeModifiers.contains(Modifier.PRIVATE) || typeModifiers.contains(Modifier.ABSTRACT)) {
        error(
            "Classes declaring @Provides methods must not be private or abstract: "
                + type.getQualifiedName(),
            type);
        continue;
      }

      Set<Modifier> methodModifiers = providerMethod.getModifiers();
      if (methodModifiers.contains(Modifier.PRIVATE)
          || methodModifiers.contains(Modifier.ABSTRACT)
          || methodModifiers.contains(Modifier.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;
      }

      List<ExecutableElement> methods = result.get(type.getQualifiedName().toString());
      if (methods == null) {
        methods = new ArrayList<ExecutableElement>();
        result.put(type.getQualifiedName().toString(), methods);
      }
      methods.add(providerMethodAsExecutable);
    }

    Elements elementUtils = processingEnv.getElementUtils();
    TypeMirror objectType = elementUtils.getTypeElement("java.lang.Object").asType();

    // Catch any stray modules without @Provides since their entry points
    // 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: " + module, module);
        continue;
      }

      TypeElement moduleType = (TypeElement) module;

      // Verify that all modules do not extend from non-Object types.
      if (!moduleType.getSuperclass().equals(objectType)) {
        error("Modules must not extend from other classes: " + module, module);
      }

      String moduleName = moduleType.getQualifiedName().toString();
      if (result.containsKey(moduleName)) continue;
      result.put(moduleName, new ArrayList<ExecutableElement>());
    }
    return result;
  }

  private Set<? extends Element> providesMethods(RoundEnvironment env) {
    Set<Element> result = new LinkedHashSet<Element>();
    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 void writeModuleAdapter(
      TypeElement type, Map<String, Object> module, List<ExecutableElement> providerMethods)
      throws IOException {
    if (module == null) {
      error(type + " has @Provides methods but no @Module annotation", type);
      return;
    }

    Object[] staticInjections = (Object[]) module.get("staticInjections");
    Object[] entryPoints = (Object[]) module.get("entryPoints");
    Object[] includes = (Object[]) module.get("includes");

    boolean overrides = (Boolean) module.get("overrides");
    boolean complete = (Boolean) module.get("complete");

    String adapterName = CodeGen.adapterName(type, MODULE_ADAPTER_SUFFIX);
    JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(adapterName, type);
    JavaWriter writer = new JavaWriter(sourceFile.openWriter());

    boolean multibindings = checkForMultibindings(providerMethods);
    boolean providerMethodDependencies = checkForDependencies(providerMethods);

    writer.emitEndOfLineComment(ProcessorJavadocs.GENERATED_BY_DAGGER);
    writer.emitPackage(CodeGen.getPackage(type).getQualifiedName().toString());
    writer.emitEmptyLine();
    writer.emitImports(
        getImports(multibindings, !providerMethods.isEmpty(), providerMethodDependencies));

    String typeName = type.getQualifiedName().toString();
    writer.emitEmptyLine();
    writer.emitJavadoc(ProcessorJavadocs.MODULE_TYPE);
    writer.beginType(
        adapterName, "class", PUBLIC | FINAL, JavaWriter.type(ModuleAdapter.class, typeName));

    StringBuilder entryPointsField = new StringBuilder().append("{ ");
    for (Object entryPoint : entryPoints) {
      TypeMirror typeMirror = (TypeMirror) entryPoint;
      String key = GeneratorKeys.rawMembersKey(typeMirror);
      entryPointsField.append(JavaWriter.stringLiteral(key)).append(", ");
    }
    entryPointsField.append("}");
    writer.emitField(
        "String[]", "ENTRY_POINTS", PRIVATE | STATIC | FINAL, entryPointsField.toString());

    StringBuilder staticInjectionsField = new StringBuilder().append("{ ");
    for (Object staticInjection : staticInjections) {
      TypeMirror typeMirror = (TypeMirror) staticInjection;
      staticInjectionsField.append(CodeGen.typeToString(typeMirror)).append(".class, ");
    }
    staticInjectionsField.append("}");
    writer.emitField(
        "Class<?>[]",
        "STATIC_INJECTIONS",
        PRIVATE | STATIC | FINAL,
        staticInjectionsField.toString());

    StringBuilder includesField = new StringBuilder().append("{ ");
    for (Object include : includes) {
      if (!(include instanceof TypeMirror)) {
        // TODO(tbroyer): pass annotation information
        processingEnv
            .getMessager()
            .printMessage(
                Diagnostic.Kind.WARNING,
                "Unexpected value: " + include + " in includes of " + type,
                type);
        continue;
      }
      TypeMirror typeMirror = (TypeMirror) include;
      includesField.append(CodeGen.typeToString(typeMirror)).append(".class, ");
    }
    includesField.append("}");
    writer.emitField("Class<?>[]", "INCLUDES", PRIVATE | STATIC | FINAL, includesField.toString());

    writer.emitEmptyLine();
    writer.beginMethod(null, adapterName, PUBLIC);
    writer.emitStatement(
        "super(ENTRY_POINTS, STATIC_INJECTIONS, %s /*overrides*/, " + "INCLUDES, %s /*complete*/)",
        overrides, complete);
    writer.endMethod();

    ExecutableElement noArgsConstructor = CodeGen.getNoArgsConstructor(type);
    if (noArgsConstructor != null && CodeGen.isCallableConstructor(noArgsConstructor)) {
      writer.emitEmptyLine();
      writer.emitAnnotation(Override.class);
      writer.beginMethod(typeName, "newModule", PROTECTED);
      writer.emitStatement("return new %s()", typeName);
      writer.endMethod();
    }
    // caches
    Map<ExecutableElement, String> methodToClassName =
        new LinkedHashMap<ExecutableElement, String>();
    Map<String, AtomicInteger> methodNameToNextId = new LinkedHashMap<String, AtomicInteger>();

    if (!providerMethods.isEmpty()) {
      writer.emitEmptyLine();
      writer.emitJavadoc(ProcessorJavadocs.GET_DEPENDENCIES_METHOD);
      writer.emitAnnotation(Override.class);
      writer.beginMethod("void", "getBindings", PUBLIC, BINDINGS_MAP, "map");

      for (ExecutableElement providerMethod : providerMethods) {
        Provides provides = providerMethod.getAnnotation(Provides.class);
        switch (provides.type()) {
          case UNIQUE:
            {
              String key = GeneratorKeys.get(providerMethod);
              writer.emitStatement(
                  "map.put(%s, new %s(module))",
                  JavaWriter.stringLiteral(key),
                  bindingClassName(providerMethod, methodToClassName, methodNameToNextId));
              break;
            }
          case SET:
            {
              String key = GeneratorKeys.getElementKey(providerMethod);
              writer.emitStatement(
                  "SetBinding.add(map, %s, new %s(module))",
                  JavaWriter.stringLiteral(key),
                  bindingClassName(providerMethod, methodToClassName, methodNameToNextId));
              break;
            }
          default:
            throw new AssertionError("Unknown @Provides type " + provides.type());
        }
      }
      writer.endMethod();
    }

    for (ExecutableElement providerMethod : providerMethods) {
      writeProvidesAdapter(writer, providerMethod, methodToClassName, methodNameToNextId);
    }

    writer.endType();
    writer.close();
  }

  private Set<String> getImports(boolean multibindings, boolean providers, boolean dependencies) {
    Set<String> imports = new LinkedHashSet<String>();
    imports.add(ModuleAdapter.class.getCanonicalName());
    if (providers) {
      imports.add(Binding.class.getCanonicalName());
      imports.add(Map.class.getCanonicalName());
      imports.add(Provider.class.getCanonicalName());
    }
    if (dependencies) {
      imports.add(Linker.class.getCanonicalName());
      imports.add(Set.class.getCanonicalName());
    }
    if (multibindings) {
      imports.add(SetBinding.class.getCanonicalName());
    }
    return imports;
  }

  private boolean checkForDependencies(List<ExecutableElement> providerMethods) {
    for (ExecutableElement element : providerMethods) {
      if (!element.getParameters().isEmpty()) {
        return true;
      }
    }
    return false;
  }

  private boolean checkForMultibindings(List<ExecutableElement> providerMethods) {
    for (ExecutableElement element : providerMethods) {
      if (element.getAnnotation(Provides.class).type() == Provides.Type.SET) {
        return true;
      }
    }
    return false;
  }

  private String bindingClassName(
      ExecutableElement providerMethod,
      Map<ExecutableElement, String> methodToClassName,
      Map<String, AtomicInteger> methodNameToNextId) {
    String className = methodToClassName.get(providerMethod);
    if (className != null) return className;

    String methodName = providerMethod.getSimpleName().toString();
    String suffix = "";
    AtomicInteger id = methodNameToNextId.get(methodName);
    if (id == null) {
      methodNameToNextId.put(methodName, new AtomicInteger(2));
    } else {
      suffix = id.toString();
      id.incrementAndGet();
    }
    String uppercaseMethodName =
        Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1);
    className = uppercaseMethodName + "ProvidesAdapter" + suffix;
    methodToClassName.put(providerMethod, className);
    return className;
  }

  private void writeProvidesAdapter(
      JavaWriter writer,
      ExecutableElement providerMethod,
      Map<ExecutableElement, String> methodToClassName,
      Map<String, AtomicInteger> methodNameToNextId)
      throws IOException {
    String methodName = providerMethod.getSimpleName().toString();
    String moduleType = CodeGen.typeToString(providerMethod.getEnclosingElement().asType());
    String className = bindingClassName(providerMethod, methodToClassName, methodNameToNextId);
    String returnType = CodeGen.typeToString(providerMethod.getReturnType());
    List<? extends VariableElement> parameters = providerMethod.getParameters();
    boolean dependent = !parameters.isEmpty();

    writer.emitEmptyLine();
    writer.emitJavadoc(binderTypeDocs(returnType, false, false, dependent));
    writer.beginType(
        className,
        "class",
        PUBLIC | FINAL | STATIC,
        JavaWriter.type(Binding.class, returnType),
        JavaWriter.type(Provider.class, returnType));
    writer.emitField(moduleType, "module", PRIVATE | FINAL);
    for (Element parameter : parameters) {
      TypeMirror parameterType = parameter.asType();
      writer.emitField(
          JavaWriter.type(Binding.class, CodeGen.typeToString(parameterType)),
          parameterName(parameter),
          PRIVATE);
    }

    writer.emitEmptyLine();
    writer.beginMethod(null, className, PUBLIC, moduleType, "module");
    boolean singleton = providerMethod.getAnnotation(Singleton.class) != null;
    String key = JavaWriter.stringLiteral(GeneratorKeys.get(providerMethod));
    String membersKey = null;
    writer.emitStatement(
        "super(%s, %s, %s, %s.class)",
        key, membersKey, (singleton ? "IS_SINGLETON" : "NOT_SINGLETON"), moduleType);
    writer.emitStatement("this.module = module");
    writer.endMethod();

    if (dependent) {
      writer.emitEmptyLine();
      writer.emitJavadoc(ProcessorJavadocs.ATTACH_METHOD);
      writer.emitAnnotation(Override.class);
      writer.emitAnnotation(SuppressWarnings.class, JavaWriter.stringLiteral("unchecked"));
      writer.beginMethod("void", "attach", PUBLIC, Linker.class.getCanonicalName(), "linker");
      for (VariableElement parameter : parameters) {
        String parameterKey = GeneratorKeys.get(parameter);
        writer.emitStatement(
            "%s = (%s) linker.requestBinding(%s, %s.class)",
            parameterName(parameter),
            writer.compressType(
                JavaWriter.type(Binding.class, CodeGen.typeToString(parameter.asType()))),
            JavaWriter.stringLiteral(parameterKey),
            writer.compressType(moduleType));
      }
      writer.endMethod();

      writer.emitEmptyLine();
      writer.emitJavadoc(ProcessorJavadocs.GET_DEPENDENCIES_METHOD);
      writer.emitAnnotation(Override.class);
      String setOfBindings = JavaWriter.type(Set.class, "Binding<?>");
      writer.beginMethod(
          "void",
          "getDependencies",
          PUBLIC,
          setOfBindings,
          "getBindings",
          setOfBindings,
          "injectMembersBindings");
      for (Element parameter : parameters) {
        writer.emitStatement("getBindings.add(%s)", parameter.getSimpleName().toString());
      }
      writer.endMethod();
    }

    writer.emitEmptyLine();
    writer.emitJavadoc(ProcessorJavadocs.GET_METHOD, returnType);
    writer.emitAnnotation(Override.class);
    writer.beginMethod(returnType, "get", PUBLIC);
    StringBuilder args = new StringBuilder();
    boolean first = true;
    for (Element parameter : parameters) {
      if (!first) args.append(", ");
      else first = false;
      args.append(String.format("%s.get()", parameter.getSimpleName().toString()));
    }
    writer.emitStatement("return module.%s(%s)", methodName, args.toString());
    writer.endMethod();

    writer.endType();
  }

  private String parameterName(Element parameter) {
    if (parameter.getSimpleName().contentEquals("module")) {
      return "parameter_" + parameter.getSimpleName().toString();
    }
    return parameter.getSimpleName().toString();
  }
}
Ejemplo n.º 3
0
  /**
   * Write a companion class for {@code type} that implements {@link ModuleAdapter} to expose its
   * provider methods.
   */
  private void writeModuleAdapter(
      TypeElement type, Map<String, Object> module, List<ExecutableElement> providerMethods)
      throws IOException {
    if (module == null) {
      error(type + " has @Provides methods but no @Module annotation", type);
      return;
    }

    Object[] staticInjections = (Object[]) module.get("staticInjections");
    Object[] entryPoints = (Object[]) module.get("entryPoints");
    Object[] includes = (Object[]) module.get("includes");

    boolean overrides = (Boolean) module.get("overrides");
    boolean complete = (Boolean) module.get("complete");

    String adapterName = CodeGen.adapterName(type, MODULE_ADAPTER_SUFFIX);
    JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(adapterName, type);
    JavaWriter writer = new JavaWriter(sourceFile.openWriter());

    boolean multibindings = checkForMultibindings(providerMethods);
    boolean providerMethodDependencies = checkForDependencies(providerMethods);

    writer.emitEndOfLineComment(ProcessorJavadocs.GENERATED_BY_DAGGER);
    writer.emitPackage(CodeGen.getPackage(type).getQualifiedName().toString());
    writer.emitEmptyLine();
    writer.emitImports(
        getImports(multibindings, !providerMethods.isEmpty(), providerMethodDependencies));

    String typeName = type.getQualifiedName().toString();
    writer.emitEmptyLine();
    writer.emitJavadoc(ProcessorJavadocs.MODULE_TYPE);
    writer.beginType(
        adapterName, "class", PUBLIC | FINAL, JavaWriter.type(ModuleAdapter.class, typeName));

    StringBuilder entryPointsField = new StringBuilder().append("{ ");
    for (Object entryPoint : entryPoints) {
      TypeMirror typeMirror = (TypeMirror) entryPoint;
      String key = GeneratorKeys.rawMembersKey(typeMirror);
      entryPointsField.append(JavaWriter.stringLiteral(key)).append(", ");
    }
    entryPointsField.append("}");
    writer.emitField(
        "String[]", "ENTRY_POINTS", PRIVATE | STATIC | FINAL, entryPointsField.toString());

    StringBuilder staticInjectionsField = new StringBuilder().append("{ ");
    for (Object staticInjection : staticInjections) {
      TypeMirror typeMirror = (TypeMirror) staticInjection;
      staticInjectionsField.append(CodeGen.typeToString(typeMirror)).append(".class, ");
    }
    staticInjectionsField.append("}");
    writer.emitField(
        "Class<?>[]",
        "STATIC_INJECTIONS",
        PRIVATE | STATIC | FINAL,
        staticInjectionsField.toString());

    StringBuilder includesField = new StringBuilder().append("{ ");
    for (Object include : includes) {
      if (!(include instanceof TypeMirror)) {
        // TODO(tbroyer): pass annotation information
        processingEnv
            .getMessager()
            .printMessage(
                Diagnostic.Kind.WARNING,
                "Unexpected value: " + include + " in includes of " + type,
                type);
        continue;
      }
      TypeMirror typeMirror = (TypeMirror) include;
      includesField.append(CodeGen.typeToString(typeMirror)).append(".class, ");
    }
    includesField.append("}");
    writer.emitField("Class<?>[]", "INCLUDES", PRIVATE | STATIC | FINAL, includesField.toString());

    writer.emitEmptyLine();
    writer.beginMethod(null, adapterName, PUBLIC);
    writer.emitStatement(
        "super(ENTRY_POINTS, STATIC_INJECTIONS, %s /*overrides*/, " + "INCLUDES, %s /*complete*/)",
        overrides, complete);
    writer.endMethod();

    ExecutableElement noArgsConstructor = CodeGen.getNoArgsConstructor(type);
    if (noArgsConstructor != null && CodeGen.isCallableConstructor(noArgsConstructor)) {
      writer.emitEmptyLine();
      writer.emitAnnotation(Override.class);
      writer.beginMethod(typeName, "newModule", PROTECTED);
      writer.emitStatement("return new %s()", typeName);
      writer.endMethod();
    }
    // caches
    Map<ExecutableElement, String> methodToClassName =
        new LinkedHashMap<ExecutableElement, String>();
    Map<String, AtomicInteger> methodNameToNextId = new LinkedHashMap<String, AtomicInteger>();

    if (!providerMethods.isEmpty()) {
      writer.emitEmptyLine();
      writer.emitJavadoc(ProcessorJavadocs.GET_DEPENDENCIES_METHOD);
      writer.emitAnnotation(Override.class);
      writer.beginMethod("void", "getBindings", PUBLIC, BINDINGS_MAP, "map");

      for (ExecutableElement providerMethod : providerMethods) {
        Provides provides = providerMethod.getAnnotation(Provides.class);
        switch (provides.type()) {
          case UNIQUE:
            {
              String key = GeneratorKeys.get(providerMethod);
              writer.emitStatement(
                  "map.put(%s, new %s(module))",
                  JavaWriter.stringLiteral(key),
                  bindingClassName(providerMethod, methodToClassName, methodNameToNextId));
              break;
            }
          case SET:
            {
              String key = GeneratorKeys.getElementKey(providerMethod);
              writer.emitStatement(
                  "SetBinding.add(map, %s, new %s(module))",
                  JavaWriter.stringLiteral(key),
                  bindingClassName(providerMethod, methodToClassName, methodNameToNextId));
              break;
            }
          default:
            throw new AssertionError("Unknown @Provides type " + provides.type());
        }
      }
      writer.endMethod();
    }

    for (ExecutableElement providerMethod : providerMethods) {
      writeProvidesAdapter(writer, providerMethod, methodToClassName, methodNameToNextId);
    }

    writer.endType();
    writer.close();
  }