dagger2.internal.codegen.ComponentGenerator.java Source code

Java tutorial

Introduction

Here is the source code for dagger2.internal.codegen.ComponentGenerator.java

Source

/*
 * Copyright (C) 2014 Google, 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 dagger2.internal.codegen;

import com.google.auto.common.AnnotationMirrors;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.value.AutoValue;
import com.google.common.base.CaseFormat;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import com.google.common.util.concurrent.ListenableFuture;
import dagger2.Component;
import dagger2.MapKey;
import dagger2.MembersInjector;
import dagger2.internal.Factory;
import dagger2.internal.InstanceFactory;
import dagger2.internal.MapFactory;
import dagger2.internal.MapProviderFactory;
import dagger2.internal.MembersInjectors;
import dagger2.internal.ScopedProvider;
import dagger2.internal.SetFactory;
import dagger2.internal.codegen.ComponentDescriptor.BuilderSpec;
import dagger2.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
import dagger2.internal.codegen.ContributionBinding.BindingType;
import dagger2.internal.codegen.writer.ClassName;
import dagger2.internal.codegen.writer.ClassWriter;
import dagger2.internal.codegen.writer.ConstructorWriter;
import dagger2.internal.codegen.writer.FieldWriter;
import dagger2.internal.codegen.writer.JavaWriter;
import dagger2.internal.codegen.writer.MethodWriter;
import dagger2.internal.codegen.writer.ParameterizedTypeName;
import dagger2.internal.codegen.writer.Snippet;
import dagger2.internal.codegen.writer.StringLiteral;
import dagger2.internal.codegen.writer.TypeName;
import dagger2.internal.codegen.writer.TypeNames;
import dagger2.internal.codegen.writer.TypeWriter;
import dagger2.internal.codegen.writer.VoidName;
import dagger2.producers.Producer;
import dagger2.producers.internal.Producers;
import dagger2.producers.internal.SetProducer;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Generated;
import javax.annotation.processing.Filer;
import javax.inject.Provider;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.AnnotationValueVisitor;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementKindVisitor6;
import javax.lang.model.util.SimpleAnnotationValueVisitor6;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

import static com.google.auto.common.MoreTypes.asDeclared;
import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verify;
import static dagger2.internal.codegen.Binding.bindingPackageFor;
import static dagger2.internal.codegen.ConfigurationAnnotations.getMapKeys;
import static dagger2.internal.codegen.ErrorMessages.CANNOT_RETURN_NULL_FROM_NON_NULLABLE_COMPONENT_METHOD;
import static dagger2.internal.codegen.MembersInjectionBinding.Strategy.NO_OP;
import static dagger2.internal.codegen.ProvisionBinding.FactoryCreationStrategy.ENUM_INSTANCE;
import static dagger2.internal.codegen.ProvisionBinding.Kind.PROVISION;
import static dagger2.internal.codegen.SourceFiles.factoryNameForProductionBinding;
import static dagger2.internal.codegen.SourceFiles.factoryNameForProvisionBinding;
import static dagger2.internal.codegen.SourceFiles.frameworkTypeUsageStatement;
import static dagger2.internal.codegen.SourceFiles.membersInjectorNameForMembersInjectionBinding;
import static dagger2.internal.codegen.Util.componentCanMakeNewInstances;
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;
import static javax.lang.model.type.TypeKind.DECLARED;
import static javax.lang.model.type.TypeKind.VOID;

/**
 * Generates the implementation of the abstract types annotated with {@link Component}.
 *
 * @author Gregory Kick
 * @since 2.0
 */
final class ComponentGenerator extends SourceFileGenerator<BindingGraph> {
    private final Types types;
    private final Diagnostic.Kind nullableValidationType;

    ComponentGenerator(Filer filer, Types types, Diagnostic.Kind nullableValidationType) {
        super(filer);
        this.types = types;
        this.nullableValidationType = nullableValidationType;
    }

    @Override
    ClassName nameGeneratedType(BindingGraph input) {
        ClassName componentDefinitionClassName = ClassName
                .fromTypeElement(input.componentDescriptor().componentDefinitionType());
        String componentName = "Dagger" + componentDefinitionClassName.classFileName().replace('$', '_');
        return componentDefinitionClassName.topLevelClassName().peerNamed(componentName);
    }

    @Override
    Iterable<? extends Element> getOriginatingElements(BindingGraph input) {
        return ImmutableSet.of(input.componentDescriptor().componentDefinitionType());
    }

    @Override
    Optional<? extends Element> getElementForErrorReporting(BindingGraph input) {
        return Optional.of(input.componentDescriptor().componentDefinitionType());
    }

    @AutoValue
    static abstract class ProxyClassAndField {
        abstract ClassWriter proxyWriter();

        abstract FieldWriter proxyFieldWriter();

        static ProxyClassAndField create(ClassWriter proxyWriter, FieldWriter proxyFieldWriter) {
            return new AutoValue_ComponentGenerator_ProxyClassAndField(proxyWriter, proxyFieldWriter);
        }
    }

    @AutoValue
    static abstract class MemberSelect {
        static MemberSelect instanceSelect(ClassName owningClass, Snippet snippet) {
            return new AutoValue_ComponentGenerator_MemberSelect(Optional.<TypeName>absent(), owningClass, false,
                    snippet);
        }

        static MemberSelect staticSelect(ClassName owningClass, Snippet snippet) {
            return new AutoValue_ComponentGenerator_MemberSelect(Optional.<TypeName>absent(), owningClass, true,
                    snippet);
        }

        static MemberSelect staticMethodInvocationWithCast(ClassName owningClass, Snippet snippet,
                TypeName castType) {
            return new AutoValue_ComponentGenerator_MemberSelect(Optional.of(castType), owningClass, true, snippet);
        }

        /**
         * This exists only to facilitate edge cases in which we need to select a member, but that
         * member uses a type parameter that can't be inferred.
         */
        abstract Optional<TypeName> selectedCast();

        abstract ClassName owningClass();

        abstract boolean staticMember();

        abstract Snippet snippet();

        private Snippet qualifiedSelectSnippet() {
            return Snippet.format("%s" + (staticMember() ? "" : ".this") + ".%s", owningClass(), snippet());
        }

        Snippet getSnippetWithRawTypeCastFor(ClassName usingClass) {
            Snippet snippet = getSnippetFor(usingClass);
            return selectedCast().isPresent() ? Snippet.format("(%s) %s", selectedCast().get(), snippet) : snippet;
        }

        Snippet getSnippetFor(ClassName usingClass) {
            return owningClass().equals(usingClass) ? snippet() : qualifiedSelectSnippet();
        }
    }

    @Override
    ImmutableSet<JavaWriter> write(ClassName componentName, BindingGraph input) {
        TypeElement componentDefinitionType = input.componentDescriptor().componentDefinitionType();
        ClassName componentDefinitionTypeName = ClassName.fromTypeElement(componentDefinitionType);

        JavaWriter writer = JavaWriter.inPackage(componentName.packageName());

        ClassWriter componentWriter = writer.addClass(componentName.simpleName());
        componentWriter.annotate(Generated.class).setValue(ComponentProcessor.class.getCanonicalName());
        componentWriter.addModifiers(PUBLIC, FINAL);
        switch (componentDefinitionType.getKind()) {
        case CLASS:
            checkState(componentDefinitionType.getModifiers().contains(ABSTRACT));
            componentWriter.setSuperType(componentDefinitionTypeName);
            break;
        case INTERFACE:
            componentWriter.addImplementedType(componentDefinitionTypeName);
            break;
        default:
            throw new IllegalStateException();
        }

        Set<JavaWriter> javaWriters = Sets.newHashSet();
        javaWriters.add(writer);
        writeComponent(input, componentDefinitionTypeName, componentWriter, javaWriters);

        return ImmutableSet.copyOf(javaWriters);
    }

    /**
     * Writes out a builder for a component or subcomponent.
     *
     * @param input the component or subcomponent
     * @param componentApiName the API name of the component we're building (not our impl)
     * @param componentImplName the implementation name of the component we're building
     * @param componentWriter the class we're adding this builder to
     * @param componentContributionFields a map of member selects so we can later use the fields
     */
    private ClassWriter writeBuilder(BindingGraph input, ClassName componentApiName, ClassName componentImplName,
            ClassWriter componentWriter, Map<TypeElement, MemberSelect> componentContributionFields) {
        ClassWriter builderWriter;
        Optional<BuilderSpec> builderSpec = input.componentDescriptor().builderSpec();
        switch (input.componentDescriptor().kind()) {
        case COMPONENT:
        case PRODUCTION_COMPONENT:
            builderWriter = componentWriter.addNestedClass("Builder");
            builderWriter.addModifiers(STATIC);

            // Only top-level components have the factory builder() method.
            // Mirror the user's builder API type if they had one.
            MethodWriter builderFactoryMethod = builderSpec.isPresent()
                    ? componentWriter.addMethod(builderSpec.get().builderDefinitionType().asType(), "builder")
                    : componentWriter.addMethod(builderWriter, "builder");
            builderFactoryMethod.addModifiers(PUBLIC, STATIC);
            builderFactoryMethod.body().addSnippet("return new %s();", builderWriter.name());
            break;
        case SUBCOMPONENT:
            verify(builderSpec.isPresent()); // we only write subcomponent builders if there was a spec
            builderWriter = componentWriter.addNestedClass(componentApiName.simpleName() + "Builder");
            break;
        default:
            throw new IllegalStateException();
        }
        builderWriter.addModifiers(FINAL);
        builderWriter.addConstructor().addModifiers(PRIVATE);
        if (builderSpec.isPresent()) {
            builderWriter.addModifiers(PRIVATE);
            TypeElement builderType = builderSpec.get().builderDefinitionType();
            switch (builderType.getKind()) {
            case CLASS:
                builderWriter.setSuperType(builderType);
                break;
            case INTERFACE:
                builderWriter.addImplementedType(builderType);
                break;
            default:
                throw new IllegalStateException("not a class or interface: " + builderType);
            }
        } else {
            builderWriter.addModifiers(PUBLIC);
        }

        // the full set of types that calling code uses to construct a component instance
        ImmutableMap<TypeElement, String> componentContributionNames = ImmutableMap
                .copyOf(Maps.asMap(
                        Sets.union(
                                Sets.union(input.transitiveModules().keySet(),
                                        input.componentDescriptor().dependencies()),
                                input.componentDescriptor().executorDependency().asSet()),
                        Functions.compose(CaseFormat.UPPER_CAMEL.converterTo(LOWER_CAMEL),
                                new Function<TypeElement, String>() {
                                    @Override
                                    public String apply(TypeElement input) {
                                        return input.getSimpleName().toString();
                                    }
                                })));

        MethodWriter buildMethod;
        if (builderSpec.isPresent()) {
            ExecutableElement specBuildMethod = builderSpec.get().buildMethod();
            // Note: we don't use the specBuildMethod.getReturnType() as the return type
            // because it might be a type variable.  We make use of covariant returns to allow
            // us to return the component type, which will always be valid.
            buildMethod = builderWriter.addMethod(componentApiName, specBuildMethod.getSimpleName().toString());
            buildMethod.annotate(Override.class);
        } else {
            buildMethod = builderWriter.addMethod(componentApiName, "build");
        }
        buildMethod.addModifiers(PUBLIC);

        for (Entry<TypeElement, String> entry : componentContributionNames.entrySet()) {
            TypeElement contributionElement = entry.getKey();
            String contributionName = entry.getValue();
            FieldWriter builderField = builderWriter.addField(contributionElement, contributionName);
            builderField.addModifiers(PRIVATE);
            componentContributionFields.put(contributionElement, MemberSelect.instanceSelect(componentImplName,
                    Snippet.format("builder.%s", builderField.name())));
            if (componentCanMakeNewInstances(contributionElement)) {
                buildMethod.body().addSnippet("if (%s == null) {", builderField.name())
                        .addSnippet("  this.%s = new %s();", builderField.name(),
                                ClassName.fromTypeElement(contributionElement))
                        .addSnippet("}");
            } else {
                buildMethod.body().addSnippet("if (%s == null) {", builderField.name())
                        .addSnippet("  throw new IllegalStateException(\"%s must be set\");", builderField.name())
                        .addSnippet("}");
            }
            MethodWriter builderMethod;
            boolean returnsVoid = false;
            if (builderSpec.isPresent()) {
                ExecutableElement method = builderSpec.get().methodMap().get(contributionElement);
                if (method == null) { // no method in the API, nothing to write out.
                    continue;
                }
                // If the return type is void, we add a method with the void return type.
                // Otherwise we use the builderWriter and take advantage of covariant returns
                // (so that we don't have to worry about setter methods that return type variables).
                if (method.getReturnType().getKind().equals(TypeKind.VOID)) {
                    returnsVoid = true;
                    builderMethod = builderWriter.addMethod(method.getReturnType(),
                            method.getSimpleName().toString());
                } else {
                    builderMethod = builderWriter.addMethod(builderWriter, method.getSimpleName().toString());
                }
                builderMethod.annotate(Override.class);
            } else {
                builderMethod = builderWriter.addMethod(builderWriter, contributionName);
            }
            // TODO(gak): Mirror the API's visibility.
            // (Makes no difference to the user since this class is private,
            //  but makes generated code prettier.)
            builderMethod.addModifiers(PUBLIC);
            builderMethod.addParameter(contributionElement, contributionName);
            builderMethod.body().addSnippet("if (%s == null) {", contributionName)
                    .addSnippet("  throw new NullPointerException(%s);", StringLiteral.forValue(contributionName))
                    .addSnippet("}").addSnippet("this.%s = %s;", builderField.name(), contributionName);
            if (!returnsVoid) {
                builderMethod.body().addSnippet("return this;");
            }
        }
        buildMethod.body().addSnippet("return new %s(this);", componentImplName);
        return builderWriter;
    }

    /** Returns true if the graph has any dependents that can't be automatically constructed. */
    private boolean requiresUserSuppliedDependents(BindingGraph input) {
        Set<TypeElement> allDependents = Sets.union(
                Sets.union(input.transitiveModules().keySet(), input.componentDescriptor().dependencies()),
                input.componentDescriptor().executorDependency().asSet());
        Set<TypeElement> userRequiredDependents = Sets.filter(allDependents, new Predicate<TypeElement>() {
            @Override
            public boolean apply(TypeElement input) {
                return !Util.componentCanMakeNewInstances(input);
            }
        });
        return !userRequiredDependents.isEmpty();
    }

    private ImmutableMap<BindingKey, MemberSelect> writeComponent(BindingGraph input,
            ClassName componentDefinitionTypeName, ClassWriter componentWriter, Set<JavaWriter> proxyWriters) {
        Map<TypeElement, MemberSelect> componentContributionFields = Maps.newHashMap();
        ClassWriter builderWriter = writeBuilder(input, componentDefinitionTypeName, componentWriter.name(),
                componentWriter, componentContributionFields);
        if (!requiresUserSuppliedDependents(input)) {
            MethodWriter factoryMethod = componentWriter.addMethod(componentDefinitionTypeName, "create");
            factoryMethod.addModifiers(PUBLIC, STATIC);
            // TODO(gak): replace this with something that doesn't allocate a builder
            factoryMethod.body().addSnippet("return builder().%s();",
                    input.componentDescriptor().builderSpec().isPresent()
                            ? input.componentDescriptor().builderSpec().get().buildMethod().getSimpleName()
                            : "build");
        }

        Map<BindingKey, MemberSelect> memberSelectSnippetsBuilder = Maps.newHashMap();
        Map<ContributionBinding, Snippet> multibindingContributionSnippetsBuilder = Maps.newHashMap();
        ImmutableSet.Builder<BindingKey> enumBindingKeysBuilder = ImmutableSet.builder();

        Map<String, ProxyClassAndField> packageProxies = Maps.newHashMap();

        writeFields(input, componentWriter, proxyWriters, memberSelectSnippetsBuilder,
                ImmutableMap.<ContributionBinding, Snippet>of(), multibindingContributionSnippetsBuilder,
                enumBindingKeysBuilder, packageProxies);

        ImmutableMap<BindingKey, MemberSelect> memberSelectSnippets = ImmutableMap
                .copyOf(memberSelectSnippetsBuilder);
        ImmutableMap<ContributionBinding, Snippet> multibindingContributionSnippets = ImmutableMap
                .copyOf(multibindingContributionSnippetsBuilder);
        ImmutableSet<BindingKey> enumBindingKeys = enumBindingKeysBuilder.build();

        ConstructorWriter constructorWriter = componentWriter.addConstructor();
        constructorWriter.addModifiers(PRIVATE);
        constructorWriter.addParameter(builderWriter, "builder");
        constructorWriter.body().addSnippet("assert builder != null;");
        initializeFrameworkTypes(input, componentWriter, constructorWriter, Optional.of(builderWriter.name()),
                componentContributionFields, memberSelectSnippets, ImmutableMap.<ContributionBinding, Snippet>of(),
                multibindingContributionSnippets);

        writeInterfaceMethods(input, componentWriter, memberSelectSnippets, enumBindingKeys);

        for (Entry<ExecutableElement, BindingGraph> subgraphEntry : input.subgraphs().entrySet()) {
            writeSubcomponent(componentWriter,
                    MoreTypes.asDeclared(input.componentDescriptor().componentDefinitionType().asType()),
                    proxyWriters, memberSelectSnippets, multibindingContributionSnippets, subgraphEntry.getKey(),
                    subgraphEntry.getValue());
        }

        return memberSelectSnippets;
    }

    private void writeSubcomponent(ClassWriter componentWriter, DeclaredType containingComponent,
            Set<JavaWriter> proxyWriters, ImmutableMap<BindingKey, MemberSelect> parentMemberSelectSnippets,
            ImmutableMap<ContributionBinding, Snippet> parentMultibindingContributionSnippets,
            ExecutableElement subcomponentFactoryMethod, BindingGraph subgraph) {
        ClassName subcomponentApiName = ClassName
                .fromTypeElement(subgraph.componentDescriptor().componentDefinitionType());
        ClassWriter subcomponentWriter = componentWriter.addNestedClass(subcomponentApiName.simpleName() + "Impl");
        subcomponentWriter.addModifiers(PRIVATE, FINAL);

        ConstructorWriter constructorWriter = subcomponentWriter.addConstructor();
        constructorWriter.addModifiers(PRIVATE);
        constructorWriter.body();

        Map<TypeElement, MemberSelect> componentContributionFields = Maps.newHashMap();
        ImmutableList.Builder<Snippet> subcomponentConstructorParameters = ImmutableList.builder();

        TypeMirror subcomponentType;
        MethodWriter componentMethod;
        Optional<ClassName> builderName;
        if (subgraph.componentDescriptor().builderSpec().isPresent()) {
            BuilderSpec spec = subgraph.componentDescriptor().builderSpec().get();
            subcomponentType = spec.componentType();
            componentMethod = componentWriter.addMethod(ClassName.fromTypeElement(spec.builderDefinitionType()),
                    subcomponentFactoryMethod.getSimpleName().toString());
            ClassWriter builderWriter = writeBuilder(subgraph, subcomponentApiName, subcomponentWriter.name(),
                    componentWriter, componentContributionFields);
            builderName = Optional.of(builderWriter.name());
            constructorWriter.addParameter(builderWriter, "builder");
            constructorWriter.body().addSnippet("assert builder != null;");
            componentMethod.body().addSnippet("return new %s();", builderWriter.name());
        } else {
            builderName = Optional.absent();
            ExecutableType resolvedMethod = MoreTypes
                    .asExecutable(types.asMemberOf(containingComponent, subcomponentFactoryMethod));
            subcomponentType = resolvedMethod.getReturnType();
            componentMethod = componentWriter.addMethod(subcomponentType,
                    subcomponentFactoryMethod.getSimpleName().toString());
            writeSubcomponentWithoutBuilder(subcomponentFactoryMethod, subgraph, subcomponentWriter,
                    constructorWriter, componentContributionFields, subcomponentConstructorParameters,
                    componentMethod, resolvedMethod);
        }
        componentMethod.addModifiers(PUBLIC);
        componentMethod.annotate(Override.class);

        TypeName subcomponentTypeName = TypeNames.forTypeMirror(subcomponentType);
        Element subcomponentElement = MoreTypes.asElement(subcomponentType);
        switch (subcomponentElement.getKind()) {
        case CLASS:
            checkState(subcomponentElement.getModifiers().contains(ABSTRACT));
            subcomponentWriter.setSuperType(subcomponentTypeName);
            break;
        case INTERFACE:
            subcomponentWriter.addImplementedType(subcomponentTypeName);
            break;
        default:
            throw new IllegalStateException();
        }

        Map<BindingKey, MemberSelect> memberSelectSnippetsBuilder = Maps.newHashMap();

        Map<ContributionBinding, Snippet> multibindingContributionSnippetsBuilder = Maps.newHashMap();
        ImmutableSet.Builder<BindingKey> enumBindingKeysBuilder = ImmutableSet.builder();

        Map<String, ProxyClassAndField> packageProxies = Maps.newHashMap();

        writeFields(subgraph, subcomponentWriter, proxyWriters, memberSelectSnippetsBuilder,
                parentMultibindingContributionSnippets, multibindingContributionSnippetsBuilder,
                enumBindingKeysBuilder, packageProxies);

        for (Entry<BindingKey, MemberSelect> parentBindingEntry : parentMemberSelectSnippets.entrySet()) {
            if (!memberSelectSnippetsBuilder.containsKey(parentBindingEntry.getKey())) {
                memberSelectSnippetsBuilder.put(parentBindingEntry.getKey(), parentBindingEntry.getValue());
            }
        }

        ImmutableMap<BindingKey, MemberSelect> memberSelectSnippets = ImmutableMap
                .copyOf(memberSelectSnippetsBuilder);
        ImmutableMap<ContributionBinding, Snippet> multibindingContributionSnippets = ImmutableMap
                .copyOf(multibindingContributionSnippetsBuilder);
        ImmutableSet<BindingKey> enumBindingKeys = enumBindingKeysBuilder.build();

        initializeFrameworkTypes(subgraph, subcomponentWriter, constructorWriter, builderName,
                componentContributionFields, memberSelectSnippets, parentMultibindingContributionSnippets,
                multibindingContributionSnippets);

        writeInterfaceMethods(subgraph, subcomponentWriter, memberSelectSnippets, enumBindingKeys);

        for (Entry<ExecutableElement, BindingGraph> subgraphEntry : subgraph.subgraphs().entrySet()) {
            writeSubcomponent(subcomponentWriter,
                    MoreTypes.asDeclared(subgraph.componentDescriptor().componentDefinitionType().asType()),
                    proxyWriters, memberSelectSnippets,
                    new ImmutableMap.Builder<ContributionBinding, Snippet>()
                            .putAll(parentMultibindingContributionSnippets).putAll(multibindingContributionSnippets)
                            .build(),
                    subgraphEntry.getKey(), subgraphEntry.getValue());
        }
    }

    private void writeSubcomponentWithoutBuilder(ExecutableElement subcomponentFactoryMethod, BindingGraph subgraph,
            ClassWriter subcomponentWriter, ConstructorWriter constructorWriter,
            Map<TypeElement, MemberSelect> componentContributionFields,
            ImmutableList.Builder<Snippet> subcomponentConstructorParameters, MethodWriter componentMethod,
            ExecutableType resolvedMethod) {
        List<? extends VariableElement> params = subcomponentFactoryMethod.getParameters();
        List<? extends TypeMirror> paramTypes = resolvedMethod.getParameterTypes();
        for (int i = 0; i < params.size(); i++) {
            VariableElement moduleVariable = params.get(i);
            TypeElement moduleTypeElement = MoreTypes.asTypeElement(paramTypes.get(i));
            TypeName moduleType = TypeNames.forTypeMirror(paramTypes.get(i));
            verify(subgraph.transitiveModules().containsKey(moduleTypeElement));
            componentMethod.addParameter(moduleType, moduleVariable.getSimpleName().toString());
            if (!componentContributionFields.containsKey(moduleTypeElement)) {
                String preferredModuleName = CaseFormat.UPPER_CAMEL.to(LOWER_CAMEL,
                        moduleTypeElement.getSimpleName().toString());
                FieldWriter contributionField = subcomponentWriter.addField(moduleTypeElement, preferredModuleName);
                contributionField.addModifiers(PRIVATE, FINAL);
                String actualModuleName = contributionField.name();
                constructorWriter.addParameter(moduleType, actualModuleName);
                constructorWriter.body().addSnippet(Snippet.format(
                        Joiner.on('\n').join("if (%s == null) {", "  throw new NullPointerException();", "}"),
                        actualModuleName));
                constructorWriter.body().addSnippet(Snippet.format("this.%1$s = %1$s;", actualModuleName));
                MemberSelect moduleSelect = MemberSelect.instanceSelect(subcomponentWriter.name(),
                        Snippet.format(actualModuleName));
                componentContributionFields.put(moduleTypeElement, moduleSelect);
                subcomponentConstructorParameters.add(Snippet.format("%s", moduleVariable.getSimpleName()));
            }
        }

        SetView<TypeElement> uninitializedModules = Sets.difference(subgraph.transitiveModules().keySet(),
                componentContributionFields.keySet());
        for (TypeElement moduleType : uninitializedModules) {
            String preferredModuleName = CaseFormat.UPPER_CAMEL.to(LOWER_CAMEL,
                    moduleType.getSimpleName().toString());
            FieldWriter contributionField = subcomponentWriter.addField(moduleType, preferredModuleName);
            contributionField.addModifiers(PRIVATE, FINAL);
            String actualModuleName = contributionField.name();
            constructorWriter.body().addSnippet(
                    Snippet.format("this.%s = new %s();", actualModuleName, ClassName.fromTypeElement(moduleType)));
            MemberSelect moduleSelect = MemberSelect.instanceSelect(subcomponentWriter.name(),
                    Snippet.format(actualModuleName));
            componentContributionFields.put(moduleType, moduleSelect);
        }

        componentMethod.body().addSnippet("return new %s(%s);", subcomponentWriter.name(),
                Snippet.makeParametersSnippet(subcomponentConstructorParameters.build()));
    }

    private void writeFields(BindingGraph input, ClassWriter componentWriter, Set<JavaWriter> proxyWriters,
            Map<BindingKey, MemberSelect> memberSelectSnippetsBuilder,
            Map<ContributionBinding, Snippet> parentMultibindingContributionSnippetsBuilder,
            Map<ContributionBinding, Snippet> multibindingContributionSnippetsBuilder,
            ImmutableSet.Builder<BindingKey> enumBindingKeysBuilder,
            Map<String, ProxyClassAndField> packageProxies) {
        for (ResolvedBindings resolvedBindings : input.resolvedBindings().values()) {
            writeField(componentWriter, proxyWriters, memberSelectSnippetsBuilder,
                    parentMultibindingContributionSnippetsBuilder, multibindingContributionSnippetsBuilder,
                    enumBindingKeysBuilder, packageProxies, resolvedBindings);
        }
    }

    private void writeField(ClassWriter componentWriter, Set<JavaWriter> proxyWriters,
            Map<BindingKey, MemberSelect> memberSelectSnippetsBuilder,
            Map<ContributionBinding, Snippet> parentMultibindingContributionSnippetsBuilder,
            Map<ContributionBinding, Snippet> multibindingContributionSnippetsBuilder,
            ImmutableSet.Builder<BindingKey> enumBindingKeysBuilder, Map<String, ProxyClassAndField> packageProxies,
            ResolvedBindings resolvedBindings) {
        BindingKey bindingKey = resolvedBindings.bindingKey();

        if (bindingKey.kind().equals(BindingKey.Kind.CONTRIBUTION)
                && resolvedBindings.ownedContributionBindings().isEmpty()
                && !ContributionBinding.bindingTypeFor(resolvedBindings.contributionBindings()).isMultibinding()) {
            return;
        }

        if (resolvedBindings.bindings().size() == 1) {
            if (bindingKey.kind().equals(BindingKey.Kind.CONTRIBUTION)) {
                ContributionBinding contributionBinding = Iterables
                        .getOnlyElement(resolvedBindings.contributionBindings());
                if (!contributionBinding.bindingType().isMultibinding()
                        && (contributionBinding instanceof ProvisionBinding)) {
                    ProvisionBinding provisionBinding = (ProvisionBinding) contributionBinding;
                    if (provisionBinding.factoryCreationStrategy().equals(ENUM_INSTANCE)
                            && !provisionBinding.scope().isPresent()) {
                        enumBindingKeysBuilder.add(bindingKey);
                        // skip keys whose factories are enum instances and aren't scoped
                        memberSelectSnippetsBuilder.put(bindingKey, MemberSelect.staticSelect(
                                factoryNameForProvisionBinding(provisionBinding), Snippet.format("create()")));
                        return;
                    }
                }
            } else if (bindingKey.kind().equals(BindingKey.Kind.MEMBERS_INJECTION)) {
                MembersInjectionBinding membersInjectionBinding = Iterables
                        .getOnlyElement(resolvedBindings.membersInjectionBindings());
                if (membersInjectionBinding.injectionStrategy().equals(NO_OP)) {
                    // TODO(gak): refactor to use enumBindingKeys throughout the generator
                    enumBindingKeysBuilder.add(bindingKey);
                    // TODO(gak): suppress the warnings in a reasonable place
                    memberSelectSnippetsBuilder.put(bindingKey,
                            MemberSelect.staticMethodInvocationWithCast(ClassName.fromClass(MembersInjectors.class),
                                    Snippet.format("noOp()"), ClassName.fromClass(MembersInjector.class)));
                    return;
                }
            }
        }

        String bindingPackage = bindingPackageFor(resolvedBindings.bindings())
                .or(componentWriter.name().packageName());

        final Optional<String> proxySelector;
        final TypeWriter classWithFields;
        final Set<Modifier> fieldModifiers;

        if (bindingPackage.equals(componentWriter.name().packageName())) {
            // no proxy
            proxySelector = Optional.absent();
            // component gets the fields
            classWithFields = componentWriter;
            // private fields
            fieldModifiers = EnumSet.of(PRIVATE);
        } else {
            // get or create the proxy
            ProxyClassAndField proxyClassAndField = packageProxies.get(bindingPackage);
            if (proxyClassAndField == null) {
                JavaWriter proxyJavaWriter = JavaWriter.inPackage(bindingPackage);
                proxyWriters.add(proxyJavaWriter);
                ClassWriter proxyWriter = proxyJavaWriter
                        .addClass(componentWriter.name().simpleName() + "_PackageProxy");
                proxyWriter.annotate(Generated.class).setValue(ComponentProcessor.class.getCanonicalName());
                proxyWriter.addModifiers(PUBLIC, FINAL);
                // create the field for the proxy in the component
                FieldWriter proxyFieldWriter = componentWriter.addField(proxyWriter.name(),
                        bindingPackage.replace('.', '_') + "_Proxy");
                proxyFieldWriter.addModifiers(PRIVATE, FINAL);
                proxyFieldWriter.setInitializer("new %s()", proxyWriter.name());
                proxyClassAndField = ProxyClassAndField.create(proxyWriter, proxyFieldWriter);
                packageProxies.put(bindingPackage, proxyClassAndField);
            }
            // add the field for the member select
            proxySelector = Optional.of(proxyClassAndField.proxyFieldWriter().name());
            // proxy gets the fields
            classWithFields = proxyClassAndField.proxyWriter();
            // public fields in the proxy
            fieldModifiers = EnumSet.of(PUBLIC);
        }

        if (bindingKey.kind().equals(BindingKey.Kind.CONTRIBUTION)) {
            ImmutableSet<? extends ContributionBinding> contributionBindings = resolvedBindings
                    .contributionBindings();
            if (ContributionBinding.bindingTypeFor(contributionBindings).isMultibinding()) {
                // note that here we rely on the order of the resolved bindings being from parent to child
                // otherwise, the numbering wouldn't work
                int contributionNumber = 0;
                for (ContributionBinding contributionBinding : contributionBindings) {
                    if (!contributionBinding.isSyntheticBinding()) {
                        contributionNumber++;
                        if (!parentMultibindingContributionSnippetsBuilder.containsKey(contributionBinding)) {
                            FrameworkField contributionBindingField = frameworkFieldForSyntheticContributionBinding(
                                    bindingKey, contributionNumber, contributionBinding);
                            FieldWriter contributionField = classWithFields.addField(
                                    contributionBindingField.frameworkType(), contributionBindingField.name());
                            contributionField.addModifiers(fieldModifiers);

                            ImmutableList<String> contributionSelectTokens = new ImmutableList.Builder<String>()
                                    .addAll(proxySelector.asSet()).add(contributionField.name()).build();
                            multibindingContributionSnippetsBuilder.put(contributionBinding,
                                    Snippet.memberSelectSnippet(contributionSelectTokens));
                        }
                    }
                }
            }
        }

        FrameworkField bindingField = frameworkFieldForResolvedBindings(resolvedBindings);
        FieldWriter frameworkField = classWithFields.addField(bindingField.frameworkType(), bindingField.name());
        frameworkField.addModifiers(fieldModifiers);

        ImmutableList<String> memberSelectTokens = new ImmutableList.Builder<String>().addAll(proxySelector.asSet())
                .add(frameworkField.name()).build();
        memberSelectSnippetsBuilder.put(bindingKey, MemberSelect.instanceSelect(componentWriter.name(),
                Snippet.memberSelectSnippet(memberSelectTokens)));
    }

    private void writeInterfaceMethods(BindingGraph input, ClassWriter componentWriter,
            ImmutableMap<BindingKey, MemberSelect> memberSelectSnippets, ImmutableSet<BindingKey> enumBindingKeys)
            throws AssertionError {
        Set<MethodSignature> interfaceMethods = Sets.newHashSet();

        for (ComponentMethodDescriptor componentMethod : input.componentDescriptor().componentMethods()) {
            if (componentMethod.dependencyRequest().isPresent()) {
                DependencyRequest interfaceRequest = componentMethod.dependencyRequest().get();
                ExecutableElement requestElement = MoreElements.asExecutable(interfaceRequest.requestElement());
                ExecutableType requestType = MoreTypes.asExecutable(types.asMemberOf(
                        MoreTypes.asDeclared(input.componentDescriptor().componentDefinitionType().asType()),
                        requestElement));
                MethodSignature signature = MethodSignature
                        .fromExecutableType(requestElement.getSimpleName().toString(), requestType);
                if (!interfaceMethods.contains(signature)) {
                    interfaceMethods.add(signature);
                    MethodWriter interfaceMethod = requestType.getReturnType().getKind().equals(VOID)
                            ? componentWriter.addMethod(VoidName.VOID, requestElement.getSimpleName().toString())
                            : componentWriter.addMethod(requestType.getReturnType(),
                                    requestElement.getSimpleName().toString());
                    interfaceMethod.annotate(Override.class);
                    interfaceMethod.addModifiers(PUBLIC);
                    BindingKey bindingKey = interfaceRequest.bindingKey();
                    switch (interfaceRequest.kind()) {
                    case MEMBERS_INJECTOR:
                        MemberSelect membersInjectorSelect = memberSelectSnippets.get(bindingKey);
                        List<? extends VariableElement> parameters = requestElement.getParameters();
                        if (parameters.isEmpty()) {
                            // we're returning the framework type
                            interfaceMethod.body().addSnippet("return %s;",
                                    membersInjectorSelect.getSnippetFor(componentWriter.name()));
                        } else {
                            VariableElement parameter = Iterables.getOnlyElement(parameters);
                            Name parameterName = parameter.getSimpleName();
                            interfaceMethod.addParameter(
                                    TypeNames.forTypeMirror(
                                            Iterables.getOnlyElement(requestType.getParameterTypes())),
                                    parameterName.toString());
                            interfaceMethod.body().addSnippet("%s.injectMembers(%s);",
                                    // in this case we know we won't need the cast because we're never going to pass
                                    // the reference to anything
                                    membersInjectorSelect.getSnippetFor(componentWriter.name()), parameterName);
                            if (!requestType.getReturnType().getKind().equals(VOID)) {
                                interfaceMethod.body().addSnippet("return %s;", parameterName);
                            }
                        }
                        break;
                    case INSTANCE:
                        if (enumBindingKeys.contains(bindingKey) && (bindingKey.key().type().getKind()
                                .equals(DECLARED)
                                && !((DeclaredType) bindingKey.key().type()).getTypeArguments().isEmpty())) {
                            // If using a parameterized enum type, then we need to store the factory
                            // in a temporary variable, in order to help javac be able to infer
                            // the generics of the Factory.create methods.
                            TypeName factoryType = ParameterizedTypeName.create(Provider.class,
                                    TypeNames.forTypeMirror(requestType.getReturnType()));
                            interfaceMethod.body().addSnippet("%s factory = %s;", factoryType,
                                    memberSelectSnippets.get(bindingKey).getSnippetFor(componentWriter.name()));
                            interfaceMethod.body().addSnippet("return factory.get();");
                            break;
                        }
                        // fall through in the else case.
                    case LAZY:
                    case PRODUCED:
                    case PRODUCER:
                    case PROVIDER:
                    case FUTURE:
                        interfaceMethod.body().addSnippet("return %s;",
                                frameworkTypeUsageStatement(
                                        memberSelectSnippets.get(bindingKey).getSnippetFor(componentWriter.name()),
                                        interfaceRequest.kind()));
                        break;
                    default:
                        throw new AssertionError();
                    }
                }
            }
        }
    }

    private void initializeFrameworkTypes(BindingGraph input, ClassWriter componentWriter,
            ConstructorWriter constructorWriter, Optional<ClassName> builderName,
            Map<TypeElement, MemberSelect> componentContributionFields,
            ImmutableMap<BindingKey, MemberSelect> memberSelectSnippets,
            ImmutableMap<ContributionBinding, Snippet> parentMultibindingContributionSnippets,
            ImmutableMap<ContributionBinding, Snippet> multibindingContributionSnippets) throws AssertionError {
        List<List<BindingKey>> partitions = Lists.partition(input.resolvedBindings().keySet().asList(), 100);
        for (int i = 0; i < partitions.size(); i++) {
            MethodWriter initializeMethod = componentWriter.addMethod(VoidName.VOID,
                    "initialize" + ((i == 0) ? "" : i));
            initializeMethod.body();
            initializeMethod.addModifiers(PRIVATE);
            if (builderName.isPresent()) {
                initializeMethod.addParameter(builderName.get(), "builder").addModifiers(FINAL);
                constructorWriter.body().addSnippet("%s(builder);", initializeMethod.name());
            } else {
                constructorWriter.body().addSnippet("%s();", initializeMethod.name());
            }

            for (BindingKey bindingKey : partitions.get(i)) {
                Snippet memberSelectSnippet = memberSelectSnippets.get(bindingKey)
                        .getSnippetFor(componentWriter.name());
                ResolvedBindings resolvedBindings = input.resolvedBindings().get(bindingKey);
                switch (bindingKey.kind()) {
                case CONTRIBUTION:
                    ImmutableSet<? extends ContributionBinding> bindings = resolvedBindings.contributionBindings();

                    switch (ContributionBinding.bindingTypeFor(bindings)) {
                    case SET:
                        boolean hasOnlyProvisions = Iterables.all(bindings,
                                Predicates.instanceOf(ProvisionBinding.class));
                        ImmutableList.Builder<Snippet> parameterSnippets = ImmutableList.builder();
                        for (ContributionBinding binding : bindings) {
                            if (multibindingContributionSnippets.containsKey(binding)) {
                                Snippet initializeSnippet = initializeFactoryForContributionBinding(binding, input,
                                        componentWriter.name(), componentContributionFields, memberSelectSnippets);
                                Snippet snippet = multibindingContributionSnippets.get(binding);
                                initializeMethod.body().addSnippet("this.%s = %s;", snippet, initializeSnippet);
                                parameterSnippets.add(snippet);
                            } else if (parentMultibindingContributionSnippets.containsKey(binding)) {
                                parameterSnippets.add(parentMultibindingContributionSnippets.get(binding));
                            } else {
                                throw new IllegalStateException(binding + " was not found in");
                            }
                        }
                        Snippet initializeSetSnippet = Snippet.format("%s.create(%s)",
                                hasOnlyProvisions ? ClassName.fromClass(SetFactory.class)
                                        : ClassName.fromClass(SetProducer.class),
                                Snippet.makeParametersSnippet(parameterSnippets.build()));
                        initializeMethod.body().addSnippet("this.%s = %s;", memberSelectSnippet,
                                initializeSetSnippet);
                        break;
                    case MAP:
                        if (Sets.filter(bindings, Predicates.instanceOf(ProductionBinding.class)).isEmpty()) {
                            @SuppressWarnings("unchecked") // checked by the instanceof filter above
                            ImmutableSet<ProvisionBinding> provisionBindings = (ImmutableSet<ProvisionBinding>) bindings;
                            for (ProvisionBinding provisionBinding : provisionBindings) {
                                if (!isNonProviderMap(provisionBinding)
                                        && multibindingContributionSnippets.containsKey(provisionBinding)) {
                                    Snippet snippet = multibindingContributionSnippets.get(provisionBinding);
                                    initializeMethod.body().addSnippet("this.%s = %s;", snippet,
                                            initializeFactoryForProvisionBinding(provisionBinding,
                                                    componentWriter.name(),
                                                    input.componentDescriptor().dependencyMethodIndex(),
                                                    componentContributionFields, memberSelectSnippets));
                                }
                            }
                            if (!provisionBindings.isEmpty()) {
                                Snippet initializeMapSnippet = initializeMapBinding(componentWriter.name(),
                                        memberSelectSnippets,
                                        new ImmutableMap.Builder<ContributionBinding, Snippet>()
                                                .putAll(parentMultibindingContributionSnippets)
                                                .putAll(multibindingContributionSnippets).build(),
                                        provisionBindings);
                                initializeMethod.body().addSnippet("this.%s = %s;", memberSelectSnippet,
                                        initializeMapSnippet);
                            }
                        } else {
                            // TODO(user): Implement producer map bindings.
                            throw new IllegalStateException("producer map bindings not implemented yet");
                        }
                        break;
                    case UNIQUE:
                        if (!resolvedBindings.ownedContributionBindings().isEmpty()) {
                            ContributionBinding binding = Iterables.getOnlyElement(bindings);
                            if (binding instanceof ProvisionBinding) {
                                ProvisionBinding provisionBinding = (ProvisionBinding) binding;
                                if (!provisionBinding.factoryCreationStrategy().equals(ENUM_INSTANCE)
                                        || provisionBinding.scope().isPresent()) {
                                    initializeMethod.body().addSnippet("this.%s = %s;", memberSelectSnippet,
                                            initializeFactoryForProvisionBinding(provisionBinding,
                                                    componentWriter.name(),
                                                    input.componentDescriptor().dependencyMethodIndex(),
                                                    componentContributionFields, memberSelectSnippets));
                                }
                            } else if (binding instanceof ProductionBinding) {
                                ProductionBinding productionBinding = (ProductionBinding) binding;
                                initializeMethod.body().addSnippet("this.%s = %s;", memberSelectSnippet,
                                        initializeFactoryForProductionBinding(productionBinding, input,
                                                componentWriter.name(),
                                                input.componentDescriptor().dependencyMethodIndex(),
                                                componentContributionFields, memberSelectSnippets));
                            } else {
                                throw new AssertionError();
                            }
                        }
                        break;
                    default:
                        throw new IllegalStateException();
                    }
                    break;
                case MEMBERS_INJECTION:
                    MembersInjectionBinding binding = Iterables
                            .getOnlyElement(resolvedBindings.membersInjectionBindings());
                    if (!binding.injectionStrategy().equals(MembersInjectionBinding.Strategy.NO_OP)) {
                        initializeMethod.body().addSnippet("this.%s = %s;", memberSelectSnippet,
                                initializeMembersInjectorForBinding(componentWriter.name(), binding,
                                        memberSelectSnippets));
                    }
                    break;
                default:
                    throw new AssertionError();
                }
            }
        }
    }

    private static FrameworkField frameworkFieldForSyntheticContributionBinding(BindingKey bindingKey,
            int contributionNumber, ContributionBinding contributionBinding) throws AssertionError {
        switch (contributionBinding.bindingType()) {
        case MAP:
            return FrameworkField.createForMapBindingContribution(contributionBinding.frameworkClass(),
                    BindingKey.create(bindingKey.kind(), contributionBinding.key()),
                    KeyVariableNamer.INSTANCE.apply(bindingKey.key()) + "Contribution" + contributionNumber);
        case SET:
            return FrameworkField.createWithTypeFromKey(contributionBinding.frameworkClass(), bindingKey,
                    KeyVariableNamer.INSTANCE.apply(bindingKey.key()) + "Contribution" + contributionNumber);
        case UNIQUE:
            return FrameworkField.createWithTypeFromKey(contributionBinding.frameworkClass(), bindingKey,
                    KeyVariableNamer.INSTANCE.apply(bindingKey.key()) + "Contribution" + contributionNumber);
        default:
            throw new AssertionError();
        }
    }

    private static Class<?> frameworkClassForResolvedBindings(ResolvedBindings resolvedBindings) {
        switch (resolvedBindings.bindingKey().kind()) {
        case CONTRIBUTION:
            for (ContributionBinding binding : resolvedBindings.contributionBindings()) {
                if (binding instanceof ProductionBinding) {
                    return Producer.class;
                }
            }
            return Provider.class;
        case MEMBERS_INJECTION:
            return MembersInjector.class;
        default:
            throw new AssertionError();
        }
    }

    private FrameworkField frameworkFieldForResolvedBindings(ResolvedBindings resolvedBindings) {
        BindingKey bindingKey = resolvedBindings.bindingKey();
        switch (bindingKey.kind()) {
        case CONTRIBUTION:
            ImmutableSet<? extends ContributionBinding> contributionBindings = resolvedBindings
                    .contributionBindings();
            BindingType bindingsType = ProvisionBinding.bindingTypeFor(contributionBindings);
            switch (bindingsType) {
            case SET:
            case MAP:
                return FrameworkField.createWithTypeFromKey(frameworkClassForResolvedBindings(resolvedBindings),
                        bindingKey, KeyVariableNamer.INSTANCE.apply(bindingKey.key()));
            case UNIQUE:
                ContributionBinding binding = Iterables.getOnlyElement(contributionBindings);
                return FrameworkField.createWithTypeFromKey(frameworkClassForResolvedBindings(resolvedBindings),
                        bindingKey, binding.bindingElement().accept(new ElementKindVisitor6<String, Void>() {
                            @Override
                            public String visitExecutableAsConstructor(ExecutableElement e, Void p) {
                                return e.getEnclosingElement().accept(this, null);
                            }

                            @Override
                            public String visitExecutableAsMethod(ExecutableElement e, Void p) {
                                return e.getSimpleName().toString();
                            }

                            @Override
                            public String visitType(TypeElement e, Void p) {
                                return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,
                                        e.getSimpleName().toString());
                            }
                        }, null));
            default:
                throw new AssertionError();
            }
        case MEMBERS_INJECTION:
            return FrameworkField.createWithTypeFromKey(MembersInjector.class, bindingKey,
                    CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,
                            Iterables.getOnlyElement(resolvedBindings.bindings()).bindingElement().getSimpleName()
                                    .toString()));
        default:
            throw new AssertionError();
        }
    }

    private Snippet initializeFactoryForContributionBinding(ContributionBinding binding, BindingGraph input,
            ClassName componentName, Map<TypeElement, MemberSelect> componentContributionFields,
            ImmutableMap<BindingKey, MemberSelect> memberSelectSnippets) {
        if (binding instanceof ProvisionBinding) {
            return initializeFactoryForProvisionBinding((ProvisionBinding) binding, componentName,
                    input.componentDescriptor().dependencyMethodIndex(), componentContributionFields,
                    memberSelectSnippets);
        } else if (binding instanceof ProductionBinding) {
            return initializeFactoryForProductionBinding((ProductionBinding) binding, input, componentName,
                    input.componentDescriptor().dependencyMethodIndex(), componentContributionFields,
                    memberSelectSnippets);
        } else {
            throw new AssertionError();
        }
    }

    private Snippet initializeFactoryForProvisionBinding(ProvisionBinding binding, ClassName componentName,
            ImmutableMap<ExecutableElement, TypeElement> dependencyMethodIndex,
            Map<TypeElement, MemberSelect> contributionFields,
            ImmutableMap<BindingKey, MemberSelect> memberSelectSnippets) {
        switch (binding.bindingKind()) {
        case COMPONENT:
            MemberSelect componentContributionSelect = contributionFields
                    .get(MoreTypes.asTypeElement(binding.key().type()));
            return Snippet.format("%s.<%s>create(%s)", ClassName.fromClass(InstanceFactory.class),
                    TypeNames.forTypeMirror(binding.key().type()),
                    componentContributionSelect != null ? componentContributionSelect.getSnippetFor(componentName)
                            : "this");
        case COMPONENT_PROVISION:
            TypeElement bindingTypeElement = dependencyMethodIndex.get(binding.bindingElement());
            String sourceFieldName = CaseFormat.UPPER_CAMEL.to(LOWER_CAMEL,
                    bindingTypeElement.getSimpleName().toString());
            if (binding.nullableType().isPresent() || nullableValidationType.equals(Diagnostic.Kind.WARNING)) {
                Snippet nullableSnippet = binding.nullableType().isPresent()
                        ? Snippet.format("@%s ", TypeNames.forTypeMirror(binding.nullableType().get()))
                        : Snippet.format("");
                return Snippet.format(
                        Joiner.on('\n').join("new %s<%2$s>() {", "  private final %6$s %7$s = %3$s;",
                                "  %5$s@Override public %2$s get() {", "    return %7$s.%4$s();", "  }", "}"),
                        ClassName.fromClass(Factory.class), TypeNames.forTypeMirror(binding.key().type()),
                        contributionFields.get(bindingTypeElement).getSnippetFor(componentName),
                        binding.bindingElement().getSimpleName().toString(), nullableSnippet,
                        TypeNames.forTypeMirror(bindingTypeElement.asType()), sourceFieldName);
            } else {
                // TODO(sameb): This throws a very vague NPE right now.  The stack trace doesn't
                // help to figure out what the method or return type is.  If we include a string
                // of the return type or method name in the error message, that can defeat obfuscation.
                // We can easily include the raw type (no generics) + annotation type (no values),
                // using .class & String.format -- but that wouldn't be the whole story.
                // What should we do?
                StringLiteral failMsg = StringLiteral
                        .forValue(CANNOT_RETURN_NULL_FROM_NON_NULLABLE_COMPONENT_METHOD);
                return Snippet.format(
                        Joiner.on('\n').join("new %s<%2$s>() {", "  private final %6$s %7$s = %3$s;",
                                "  @Override public %2$s get() {", "    %2$s provided = %7$s.%4$s();",
                                "    if (provided == null) {", "      throw new NullPointerException(%5$s);",
                                "    }", "    return provided;", "  }", "}"),
                        ClassName.fromClass(Factory.class), TypeNames.forTypeMirror(binding.key().type()),
                        contributionFields.get(bindingTypeElement).getSnippetFor(componentName),
                        binding.bindingElement().getSimpleName().toString(), failMsg,
                        TypeNames.forTypeMirror(bindingTypeElement.asType()), sourceFieldName);
            }
        case INJECTION:
        case PROVISION:
            List<Snippet> parameters = Lists.newArrayListWithCapacity(binding.dependencies().size() + 1);
            if (binding.bindingKind().equals(PROVISION)
                    && !binding.bindingElement().getModifiers().contains(STATIC)) {
                parameters.add(contributionFields.get(binding.contributedBy().get()).getSnippetFor(componentName));
            }
            parameters.addAll(
                    getDependencyParameters(componentName, binding.implicitDependencies(), memberSelectSnippets));

            Snippet factorySnippet = Snippet.format("%s.create(%s)", factoryNameForProvisionBinding(binding),
                    Snippet.makeParametersSnippet(parameters));
            return binding.scope().isPresent()
                    ? Snippet.format("%s.create(%s)", ClassName.fromClass(ScopedProvider.class), factorySnippet)
                    : factorySnippet;
        default:
            throw new AssertionError();
        }
    }

    private Snippet initializeFactoryForProductionBinding(ProductionBinding binding, BindingGraph bindingGraph,
            ClassName componentName, ImmutableMap<ExecutableElement, TypeElement> dependencyMethodIndex,
            Map<TypeElement, MemberSelect> contributionFields,
            ImmutableMap<BindingKey, MemberSelect> memberSelectSnippets) {
        switch (binding.bindingKind()) {
        case COMPONENT_PRODUCTION:
            TypeElement bindingTypeElement = dependencyMethodIndex.get(binding.bindingElement());
            String sourceFieldName = CaseFormat.UPPER_CAMEL.to(LOWER_CAMEL,
                    bindingTypeElement.getSimpleName().toString());
            return Snippet.format(
                    Joiner.on('\n').join("new %s<%2$s>() {", "  private final %6$s %7$s = %4$s;",
                            "  @Override public %3$s<%2$s> get() {", "    return %7$s.%5$s();", "  }", "}"),
                    ClassName.fromClass(Producer.class), TypeNames.forTypeMirror(binding.key().type()),
                    ClassName.fromClass(ListenableFuture.class),
                    contributionFields.get(bindingTypeElement).getSnippetFor(componentName),
                    binding.bindingElement().getSimpleName().toString(),
                    TypeNames.forTypeMirror(bindingTypeElement.asType()), sourceFieldName);
        case IMMEDIATE:
        case FUTURE_PRODUCTION:
            List<Snippet> parameters = Lists.newArrayListWithCapacity(binding.dependencies().size() + 2);
            parameters.add(contributionFields.get(binding.bindingTypeElement()).getSnippetFor(componentName));
            parameters.add(contributionFields.get(bindingGraph.componentDescriptor().executorDependency().get())
                    .getSnippetFor(componentName));
            parameters.addAll(getProducerDependencyParameters(bindingGraph, componentName, binding.dependencies(),
                    memberSelectSnippets));

            return Snippet.format("new %s(%s)", factoryNameForProductionBinding(binding),
                    Snippet.makeParametersSnippet(parameters));
        default:
            throw new AssertionError();
        }
    }

    private Snippet initializeMembersInjectorForBinding(ClassName componentName, MembersInjectionBinding binding,
            ImmutableMap<BindingKey, MemberSelect> memberSelectSnippets) {
        switch (binding.injectionStrategy()) {
        case NO_OP:
            return Snippet.format("%s.noOp()", ClassName.fromClass(MembersInjectors.class));
        case DELEGATE:
            DependencyRequest parentInjectorRequest = binding.parentInjectorRequest().get();
            return Snippet.format("%s.delegatingTo(%s)", ClassName.fromClass(MembersInjectors.class),
                    memberSelectSnippets.get(parentInjectorRequest.bindingKey()).getSnippetFor(componentName));
        case INJECT_MEMBERS:
            List<Snippet> parameters = getDependencyParameters(componentName, binding.implicitDependencies(),
                    memberSelectSnippets);
            return Snippet.format("%s.create(%s)", membersInjectorNameForMembersInjectionBinding(binding),
                    Snippet.makeParametersSnippet(parameters));
        default:
            throw new AssertionError();
        }
    }

    private List<Snippet> getDependencyParameters(ClassName componentName, Iterable<DependencyRequest> dependencies,
            ImmutableMap<BindingKey, MemberSelect> memberSelectSnippets) {
        ImmutableList.Builder<Snippet> parameters = ImmutableList.builder();
        for (Collection<DependencyRequest> requestsForKey : SourceFiles
                .indexDependenciesByUnresolvedKey(types, dependencies).asMap().values()) {
            BindingKey key = Iterables.getOnlyElement(
                    FluentIterable.from(requestsForKey).transform(new Function<DependencyRequest, BindingKey>() {
                        @Override
                        public BindingKey apply(DependencyRequest request) {
                            return request.bindingKey();
                        }
                    }).toSet());
            parameters.add(memberSelectSnippets.get(key).getSnippetWithRawTypeCastFor(componentName));
        }
        return parameters.build();
    }

    private List<Snippet> getProducerDependencyParameters(BindingGraph bindingGraph, ClassName componentName,
            Iterable<DependencyRequest> dependencies, ImmutableMap<BindingKey, MemberSelect> memberSelectSnippets) {
        ImmutableList.Builder<Snippet> parameters = ImmutableList.builder();
        for (Collection<DependencyRequest> requestsForKey : SourceFiles
                .indexDependenciesByUnresolvedKey(types, dependencies).asMap().values()) {
            BindingKey key = Iterables.getOnlyElement(
                    FluentIterable.from(requestsForKey).transform(new Function<DependencyRequest, BindingKey>() {
                        @Override
                        public BindingKey apply(DependencyRequest request) {
                            return request.bindingKey();
                        }
                    }));
            ResolvedBindings resolvedBindings = bindingGraph.resolvedBindings().get(key);
            Class<?> frameworkClass = DependencyRequestMapper.FOR_PRODUCER.getFrameworkClass(requestsForKey);
            if (frameworkClassForResolvedBindings(resolvedBindings).equals(Provider.class)
                    && frameworkClass.equals(Producer.class)) {
                parameters.add(Snippet.format("%s.producerFromProvider(%s)", ClassName.fromClass(Producers.class),
                        memberSelectSnippets.get(key).getSnippetFor(componentName)));
            } else {
                parameters.add(memberSelectSnippets.get(key).getSnippetFor(componentName));
            }
        }
        return parameters.build();
    }

    private Snippet initializeMapBinding(ClassName componentName,
            ImmutableMap<BindingKey, MemberSelect> memberSelectSnippets,
            ImmutableMap<ContributionBinding, Snippet> multibindingContributionSnippets,
            Set<ProvisionBinding> bindings) {
        Iterator<ProvisionBinding> iterator = bindings.iterator();
        // get type information from first binding in iterator
        ProvisionBinding firstBinding = iterator.next();
        if (isNonProviderMap(firstBinding)) {
            return Snippet.format("%s.create(%s)", ClassName.fromClass(MapFactory.class),
                    memberSelectSnippets.get(Iterables.getOnlyElement(firstBinding.dependencies()).bindingKey())
                            .getSnippetFor(componentName));
        } else {
            DeclaredType mapType = asDeclared(firstBinding.key().type());
            TypeMirror mapKeyType = Util.getKeyTypeOfMap(mapType);
            TypeMirror mapValueType = Util.getProvidedValueTypeOfMap(mapType); // V of Map<K, Provider<V>>
            StringBuilder snippetFormatBuilder = new StringBuilder("%s.<%s, %s>builder(%d)");
            for (int i = 0; i < bindings.size(); i++) {
                snippetFormatBuilder.append("\n    .put(%s, %s)");
            }
            snippetFormatBuilder.append("\n    .build()");

            List<Object> argsBuilder = Lists.newArrayList();
            argsBuilder.add(ClassName.fromClass(MapProviderFactory.class));
            argsBuilder.add(TypeNames.forTypeMirror(mapKeyType));
            argsBuilder.add(TypeNames.forTypeMirror(mapValueType));
            argsBuilder.add(bindings.size());

            writeEntry(argsBuilder, firstBinding, multibindingContributionSnippets.get(firstBinding));
            while (iterator.hasNext()) {
                ProvisionBinding binding = iterator.next();
                writeEntry(argsBuilder, binding, multibindingContributionSnippets.get(binding));
            }

            return Snippet.format(snippetFormatBuilder.toString(), argsBuilder.toArray(new Object[0]));
        }
    }

    // add one map entry for map Provider in Constructor
    private void writeEntry(List<Object> argsBuilder, Binding binding, Snippet factory) {
        AnnotationMirror mapKeyAnnotationMirror = Iterables.getOnlyElement(getMapKeys(binding.bindingElement()));
        Map<? extends ExecutableElement, ? extends AnnotationValue> map = mapKeyAnnotationMirror.getElementValues();
        MapKey mapKey = mapKeyAnnotationMirror.getAnnotationType().asElement().getAnnotation(MapKey.class);
        if (!mapKey.unwrapValue()) {// wrapped key case
            FluentIterable<AnnotationValue> originIterable = FluentIterable
                    .from(AnnotationMirrors.getAnnotationValuesWithDefaults(mapKeyAnnotationMirror).values());
            FluentIterable<Snippet> annotationValueNames = originIterable
                    .transform(new Function<AnnotationValue, Snippet>() {
                        @Override
                        public Snippet apply(AnnotationValue value) {
                            return getValueSnippet(value);
                        }
                    });
            ImmutableList.Builder<Snippet> snippets = ImmutableList.builder();
            for (Snippet snippet : annotationValueNames) {
                snippets.add(snippet);
            }
            argsBuilder.add(Snippet.format("%s.create(%s)",
                    Util.getMapKeyCreatorClassName(
                            MoreTypes.asTypeElement(mapKeyAnnotationMirror.getAnnotationType())),
                    Snippet.makeParametersSnippet(snippets.build())));
            argsBuilder.add(factory);
        } else { // unwrapped key case
            argsBuilder.add(Iterables.getOnlyElement(map.entrySet()).getValue());
            argsBuilder.add(factory);
        }
    }

    // Get the Snippet representation of a Annotation Value
    // TODO(user) write corresponding test to verify the AnnotationValueVisitor is right
    private Snippet getValueSnippet(AnnotationValue value) {
        AnnotationValueVisitor<Snippet, Void> mapKeyVisitor = new SimpleAnnotationValueVisitor6<Snippet, Void>() {
            @Override
            public Snippet visitEnumConstant(VariableElement c, Void p) {
                return Snippet.format("%s.%s", TypeNames.forTypeMirror(c.getEnclosingElement().asType()),
                        c.getSimpleName());
            }

            @Override
            public Snippet visitAnnotation(AnnotationMirror a, Void p) {
                if (a.getElementValues().isEmpty()) {
                    return Snippet.format("@%s", TypeNames.forTypeMirror(a.getAnnotationType()));
                } else {
                    Map<ExecutableElement, AnnotationValue> map = AnnotationMirrors
                            .getAnnotationValuesWithDefaults(a);
                    // build "@Annotation(a = , b = , c = ))
                    ImmutableList.Builder<Snippet> snippets = ImmutableList.builder();
                    for (Entry<ExecutableElement, AnnotationValue> entry : map.entrySet()) {
                        snippets.add(Snippet.format("%s = %s", TypeNames.forTypeMirror(entry.getKey().asType()),
                                getValueSnippet(entry.getValue())));

                    }
                    return Snippet.format("@%s(%s)", TypeNames.forTypeMirror(a.getAnnotationType()),
                            Snippet.makeParametersSnippet(snippets.build()));
                }
            }

            @Override
            public Snippet visitType(TypeMirror t, Void p) {
                return Snippet.format("%s.class", TypeNames.forTypeMirror(t));
            }

            @Override
            public Snippet visitString(String s, Void p) {
                return Snippet.format("\"%s\"", s);
            }

            @Override
            protected Snippet defaultAction(Object o, Void v) {
                return Snippet.format("%s", o);
            }

            @Override
            public Snippet visitArray(List<? extends AnnotationValue> values, Void v) {
                ImmutableList.Builder<Snippet> snippets = ImmutableList.builder();
                for (int i = 0; i < values.size(); i++) {
                    snippets.add(values.get(i).accept(this, null));
                }
                return Snippet.format("[%s]", Snippet.makeParametersSnippet(snippets.build()));
            }
        };
        return value.accept(mapKeyVisitor, null);
    }

    private boolean isNonProviderMap(Binding binding) {
        TypeMirror bindingType = binding.key().type();
        return MoreTypes.isTypeOf(Map.class, bindingType) // Implicitly guarantees a declared type.
                && !MoreTypes.isTypeOf(Provider.class, asDeclared(bindingType).getTypeArguments().get(1));
    }
}