/*
* Copyright (C) 2015 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 dagger.internal.codegen;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
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.util.concurrent.ListenableFuture;
import dagger.MembersInjector;
import dagger.internal.DelegateFactory;
import dagger.internal.Factory;
import dagger.internal.InstanceFactory;
import dagger.internal.MapFactory;
import dagger.internal.MapProviderFactory;
import dagger.internal.MembersInjectors;
import dagger.internal.ScopedProvider;
import dagger.internal.SetFactory;
import dagger.internal.codegen.ComponentDescriptor.BuilderSpec;
import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
import dagger.internal.codegen.ComponentGenerator.MemberSelect;
import dagger.internal.codegen.writer.ClassName;
import dagger.internal.codegen.writer.ClassWriter;
import dagger.internal.codegen.writer.ConstructorWriter;
import dagger.internal.codegen.writer.FieldWriter;
import dagger.internal.codegen.writer.JavaWriter;
import dagger.internal.codegen.writer.MethodWriter;
import dagger.internal.codegen.writer.ParameterizedTypeName;
import dagger.internal.codegen.writer.Snippet;
import dagger.internal.codegen.writer.StringLiteral;
import dagger.internal.codegen.writer.TypeName;
import dagger.internal.codegen.writer.TypeNames;
import dagger.internal.codegen.writer.VoidName;
import dagger.producers.Producer;
import dagger.producers.internal.Producers;
import dagger.producers.internal.SetOfProducedProducer;
import dagger.producers.internal.SetProducer;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Provider;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
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.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;
import static com.google.auto.common.MoreTypes.asDeclared;
import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_CAMEL;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.AbstractComponentWriter.InitializationState.DELEGATED;
import static dagger.internal.codegen.AbstractComponentWriter.InitializationState.INITIALIZED;
import static dagger.internal.codegen.AbstractComponentWriter.InitializationState.UNINITIALIZED;
import static dagger.internal.codegen.Binding.bindingPackageFor;
import static dagger.internal.codegen.ComponentGenerator.MemberSelect.staticMethodInvocationWithCast;
import static dagger.internal.codegen.ComponentGenerator.MemberSelect.staticSelect;
import static dagger.internal.codegen.ContributionBinding.contributionTypeFor;
import static dagger.internal.codegen.ContributionBinding.FactoryCreationStrategy.ENUM_INSTANCE;
import static dagger.internal.codegen.ContributionBinding.Kind.PROVISION;
import static dagger.internal.codegen.ErrorMessages.CANNOT_RETURN_NULL_FROM_NON_NULLABLE_COMPONENT_METHOD;
import static dagger.internal.codegen.MapKeys.getMapKeySnippet;
import static dagger.internal.codegen.MembersInjectionBinding.Strategy.NO_OP;
import static dagger.internal.codegen.SourceFiles.frameworkTypeUsageStatement;
import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding;
import static dagger.internal.codegen.SourceFiles.indexDependenciesByUnresolvedKey;
import static dagger.internal.codegen.SourceFiles.membersInjectorNameForType;
import static dagger.internal.codegen.Util.componentCanMakeNewInstances;
import static dagger.internal.codegen.Util.getKeyTypeOfMap;
import static dagger.internal.codegen.Util.getProvidedValueTypeOfMap;
import static dagger.internal.codegen.Util.isMapWithNonProvidedValues;
import static dagger.internal.codegen.writer.Snippet.makeParametersSnippet;
import static dagger.internal.codegen.writer.Snippet.memberSelectSnippet;
import static dagger.internal.codegen.writer.Snippet.nullCheck;
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;
/**
* Creates the implementation class for a component or subcomponent.
*/
abstract class AbstractComponentWriter {
// TODO(dpb): Make all these fields private after refactoring is complete.
protected final Elements elements;
protected final Types types;
protected final Key.Factory keyFactory;
protected final Kind nullableValidationType;
protected final Set<JavaWriter> javaWriters = new LinkedHashSet<>();
protected final ClassName name;
protected final BindingGraph graph;
private final Map<BindingKey, InitializationState> initializationStates = new HashMap<>();
private final Map<Binding, InitializationState> contributionInitializationStates =
new HashMap<>();
protected ClassWriter componentWriter;
private final Map<BindingKey, MemberSelect> memberSelectSnippets = new HashMap<>();
private final Map<ContributionBinding, MemberSelect> multibindingContributionSnippets =
new HashMap<>();
protected ConstructorWriter constructorWriter;
protected Optional<ClassName> builderName = Optional.absent();
/**
* For each component requirement, the builder field. This map is empty for subcomponents that do
* not use a builder.
*/
private ImmutableMap<TypeElement, FieldWriter> builderFields = ImmutableMap.of();
/**
* For each component requirement, the snippet for the component field that holds it.
*
* <p>Fields are written for all requirements for subcomponents that do not use a builder, and for
* any requirement that is reused from a subcomponent of this component.
*/
protected final Map<TypeElement, MemberSelect> componentContributionFields = Maps.newHashMap();
AbstractComponentWriter(
Types types,
Elements elements,
Key.Factory keyFactory,
Diagnostic.Kind nullableValidationType,
ClassName name,
BindingGraph graph) {
this.types = types;
this.elements = elements;
this.keyFactory = keyFactory;
this.nullableValidationType = nullableValidationType;
this.name = name;
this.graph = graph;
}
protected final TypeElement componentDefinitionType() {
return graph.componentDescriptor().componentDefinitionType();
}
protected final ClassName componentDefinitionTypeName() {
return ClassName.fromTypeElement(componentDefinitionType());
}
/**
* Returns an expression snippet that evaluates to an instance of the contribution, looking for
* either a builder field or a component field.
*/
private Snippet getComponentContributionSnippet(TypeElement contributionType) {
if (builderFields.containsKey(contributionType)) {
return Snippet.format("builder.%s", builderFields.get(contributionType).name());
} else {
Optional<Snippet> snippet = getOrCreateComponentContributionFieldSnippet(contributionType);
checkState(snippet.isPresent(), "no builder or component field for %s", contributionType);
return snippet.get();
}
}
/**
* Returns a snippet for a component contribution field. Adds a field the first time one is
* requested for a contribution type if this component's builder has a field for it.
*/
protected Optional<Snippet> getOrCreateComponentContributionFieldSnippet(
TypeElement contributionType) {
MemberSelect fieldSelect = componentContributionFields.get(contributionType);
if (fieldSelect == null) {
if (!builderFields.containsKey(contributionType)) {
return Optional.absent();
}
FieldWriter componentField =
componentWriter.addField(contributionType, simpleVariableName(contributionType));
componentField.addModifiers(PRIVATE, FINAL);
constructorWriter
.body()
.addSnippet(
"this.%s = builder.%s;",
componentField.name(),
builderFields.get(contributionType).name());
fieldSelect = MemberSelect.instanceSelect(name, Snippet.format("%s", componentField.name()));
componentContributionFields.put(contributionType, fieldSelect);
}
return Optional.of(fieldSelect.getSnippetFor(name));
}
private Snippet getMemberSelectSnippet(BindingKey key) {
return getMemberSelect(key).getSnippetFor(name);
}
protected MemberSelect getMemberSelect(BindingKey key) {
return memberSelectSnippets.get(key);
}
protected Optional<MemberSelect> getMultibindingContributionSnippet(ContributionBinding binding) {
return Optional.fromNullable(multibindingContributionSnippets.get(binding));
}
/**
* Returns the initialization state of the factory field for a binding key in this component.
*/
protected InitializationState getInitializationState(BindingKey bindingKey) {
return initializationStates.containsKey(bindingKey)
? initializationStates.get(bindingKey)
: UNINITIALIZED;
}
private void setInitializationState(BindingKey bindingKey, InitializationState state) {
initializationStates.put(bindingKey, state);
}
private InitializationState getContributionInitializationState(Binding binding) {
return contributionInitializationStates.containsKey(binding)
? contributionInitializationStates.get(binding)
: UNINITIALIZED;
}
private void setContributionInitializationState(Binding binding, InitializationState state) {
contributionInitializationStates.put(binding, state);
}
ImmutableSet<JavaWriter> write() {
if (javaWriters.isEmpty()) {
writeComponent();
}
return ImmutableSet.copyOf(javaWriters);
}
private void writeComponent() {
componentWriter = createComponentClass();
addConstructor();
addBuilder();
addFactoryMethods();
addFields();
initializeFrameworkTypes();
implementInterfaceMethods();
addSubcomponents();
}
/**
* Creates the component implementation class.
*/
protected abstract ClassWriter createComponentClass();
private void addConstructor() {
constructorWriter = componentWriter.addConstructor();
constructorWriter.addModifiers(PRIVATE);
}
/**
* Adds a builder type.
*/
protected void addBuilder() {
ClassWriter builderWriter = createBuilder();
builderWriter.addModifiers(FINAL);
builderWriter.addConstructor().addModifiers(PRIVATE);
builderName = Optional.of(builderWriter.name());
Optional<BuilderSpec> builderSpec = graph.componentDescriptor().builderSpec();
if (builderSpec.isPresent()) {
builderWriter.addModifiers(PRIVATE);
builderWriter.setSupertype(builderSpec.get().builderDefinitionType());
} else {
builderWriter.addModifiers(PUBLIC);
}
builderFields = addBuilderFields(builderWriter);
addBuildMethod(builderWriter, builderSpec);
addBuilderMethods(builderWriter, builderSpec);
constructorWriter.addParameter(builderWriter, "builder");
constructorWriter.body().addSnippet("assert builder != null;");
}
/**
* Adds fields for each of the {@linkplain BindingGraph#componentRequirements component
* requirements}. Regardless of builder spec, there is always one field per requirement.
*/
private ImmutableMap<TypeElement, FieldWriter> addBuilderFields(ClassWriter builderWriter) {
ImmutableMap.Builder<TypeElement, FieldWriter> builderFieldsBuilder = ImmutableMap.builder();
for (TypeElement contributionElement : graph.componentRequirements()) {
String contributionName = simpleVariableName(contributionElement);
FieldWriter builderField = builderWriter.addField(contributionElement, contributionName);
builderField.addModifiers(PRIVATE);
builderFieldsBuilder.put(contributionElement, builderField);
}
return builderFieldsBuilder.build();
}
/** Adds the build method to the builder. */
private void addBuildMethod(ClassWriter builderWriter, Optional<BuilderSpec> builderSpec) {
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(
componentDefinitionTypeName(), specBuildMethod.getSimpleName().toString());
buildMethod.annotate(Override.class);
} else {
buildMethod = builderWriter.addMethod(componentDefinitionTypeName(), "build");
}
buildMethod.addModifiers(PUBLIC);
for (Map.Entry<TypeElement, FieldWriter> builderFieldEntry : builderFields.entrySet()) {
FieldWriter builderField = builderFieldEntry.getValue();
if (componentCanMakeNewInstances(builderFieldEntry.getKey())) {
buildMethod.body()
.addSnippet("if (%1$s == null) { this.%1$s = new %2$s(); }",
builderField.name(),
builderField.type());
} else {
buildMethod.body()
.addSnippet(
"if (%s == null) { throw new %s(%s.class.getCanonicalName() + \" must be set\"); }",
builderField.name(),
ClassName.fromClass(IllegalStateException.class),
builderField.type());
}
}
buildMethod.body().addSnippet("return new %s(this);", name);
}
/**
* Adds the methods that set each of parameters on the builder. If the {@link BuilderSpec} is
* present, it will tailor the methods to match the spec.
*/
private void addBuilderMethods(
ClassWriter builderWriter,
Optional<BuilderSpec> builderSpec) {
if (builderSpec.isPresent()) {
for (Map.Entry<TypeElement, ExecutableElement> builderMethodEntry :
builderSpec.get().methodMap().entrySet()) {
TypeElement builderMethodType = builderMethodEntry.getKey();
ExecutableElement specMethod = builderMethodEntry.getValue();
MethodWriter builderMethod = addBuilderMethodFromSpec(builderWriter, specMethod);
String parameterName =
Iterables.getOnlyElement(specMethod.getParameters()).getSimpleName().toString();
builderMethod.addParameter(builderMethodType, parameterName);
builderMethod.body().addSnippet(nullCheck(parameterName));
if (graph.componentRequirements().contains(builderMethodType)) {
// required type
builderMethod.body().addSnippet("this.%s = %s;",
builderFields.get(builderMethodType).name(),
parameterName);
addBuilderMethodReturnStatementForSpec(specMethod, builderMethod);
} else if (graph.ownedModuleTypes().contains(builderMethodType)) {
// owned, but not required
builderMethod.body()
.addSnippet("// This module is declared, but not used in the component. "
+ "This method is a no-op");
addBuilderMethodReturnStatementForSpec(specMethod, builderMethod);
} else {
// neither owned nor required, so it must be an inherited module
builderMethod
.body()
.addSnippet(
"throw new %s(%s.format(%s, %s.class.getCanonicalName()));",
ClassName.fromClass(UnsupportedOperationException.class),
ClassName.fromClass(String.class),
StringLiteral.forValue(
"%s cannot be set because it is inherited from the enclosing component"),
ClassName.fromTypeElement(builderMethodType));
}
}
} else {
for (TypeElement componentRequirement : graph.availableDependencies()) {
String componentRequirementName = simpleVariableName(componentRequirement);
MethodWriter builderMethod = builderWriter.addMethod(
builderWriter.name(),
componentRequirementName);
builderMethod.addModifiers(PUBLIC);
builderMethod.addParameter(componentRequirement, componentRequirementName);
builderMethod.body().addSnippet(nullCheck(componentRequirementName));
if (graph.componentRequirements().contains(componentRequirement)) {
builderMethod.body()
.addSnippet("this.%s = %s;",
builderFields.get(componentRequirement).name(),
componentRequirementName);
} else {
builderMethod.annotate(Deprecated.class);
}
builderMethod.body().addSnippet("return this;");
}
}
}
private void addBuilderMethodReturnStatementForSpec(
ExecutableElement specMethod, MethodWriter builderMethod) {
if (!specMethod.getReturnType().getKind().equals(VOID)) {
builderMethod.body().addSnippet("return this;");
}
}
private MethodWriter addBuilderMethodFromSpec(
ClassWriter builderWriter, ExecutableElement method) {
String methodName = method.getSimpleName().toString();
TypeMirror returnType = method.getReturnType();
// 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).
MethodWriter builderMethod =
returnType.getKind().equals(TypeKind.VOID)
? builderWriter.addMethod(returnType, methodName)
: builderWriter.addMethod(builderWriter, methodName);
builderMethod.annotate(Override.class);
builderMethod.addModifiers(Sets.difference(method.getModifiers(), ImmutableSet.of(ABSTRACT)));
return builderMethod;
}
/**
* Creates the builder class.
*/
protected abstract ClassWriter createBuilder();
/**
* Adds component factory methods.
*/
protected abstract void addFactoryMethods();
private void addFields() {
for (ResolvedBindings resolvedBindings : graph.resolvedBindings().values()) {
addField(resolvedBindings);
}
}
private void addField(ResolvedBindings resolvedBindings) {
BindingKey bindingKey = resolvedBindings.bindingKey();
// No field needed if there are no owned bindings.
if (resolvedBindings.ownedBindings().isEmpty()) {
return;
}
// No field needed for bindings with no dependencies or state.
Optional<MemberSelect> staticMemberSelect = staticMemberSelect(resolvedBindings);
if (staticMemberSelect.isPresent()) {
memberSelectSnippets.put(bindingKey, staticMemberSelect.get());
return;
}
Optional<String> bindingPackage = bindingPackageFor(resolvedBindings.bindings());
boolean useRawType = bindingPackage.isPresent()
&& !bindingPackage.get().equals(name.packageName());
if (bindingKey.kind().equals(BindingKey.Kind.CONTRIBUTION)) {
ImmutableSet<ContributionBinding> contributionBindings =
resolvedBindings.contributionBindings();
if (ContributionBinding.contributionTypeFor(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 (resolvedBindings.ownedContributionBindings().contains(contributionBinding)) {
FrameworkField contributionBindingField =
FrameworkField.createForSyntheticContributionBinding(
contributionNumber, contributionBinding);
FieldWriter contributionField =
addFrameworkField(useRawType, contributionBindingField);
ImmutableList<String> contributionSelectTokens =
new ImmutableList.Builder<String>()
.add(contributionField.name())
.build();
multibindingContributionSnippets.put(
contributionBinding,
MemberSelect.instanceSelect(name, memberSelectSnippet(contributionSelectTokens)));
}
}
}
}
}
FrameworkField bindingField = FrameworkField.createForResolvedBindings(resolvedBindings);
FieldWriter frameworkField = addFrameworkField(useRawType, bindingField);
ImmutableList<String> memberSelectTokens =
new ImmutableList.Builder<String>()
.add(frameworkField.name())
.build();
memberSelectSnippets.put(
bindingKey,
MemberSelect.instanceSelect(name, Snippet.memberSelectSnippet(memberSelectTokens)));
}
private FieldWriter addFrameworkField(boolean useRawType,
FrameworkField contributionBindingField) {
FieldWriter contributionField =
componentWriter.addField(
useRawType
? contributionBindingField.frameworkType().type()
: contributionBindingField.frameworkType(),
contributionBindingField.name());
contributionField.addModifiers(PRIVATE);
if (useRawType) {
contributionField.annotate(SuppressWarnings.class).setValue("rawtypes");
}
return contributionField;
}
/**
* If {@code resolvedBindings} is an unscoped provision binding with no factory arguments or a
* no-op members injection binding, then we don't need a field to hold its factory. In that case,
* this method returns the static member select snippet that returns the factory or no-op members
* injector.
*/
private Optional<MemberSelect> staticMemberSelect(ResolvedBindings resolvedBindings) {
switch (resolvedBindings.bindingKey().kind()) {
case CONTRIBUTION:
if (resolvedBindings.contributionBindings().size() != 1) {
return Optional.absent();
}
ContributionBinding contributionBinding =
getOnlyElement(resolvedBindings.contributionBindings());
if (contributionBinding.contributionType().isMultibinding()
|| !(contributionBinding.bindingType().equals(Binding.Type.PROVISION))) {
return Optional.absent();
}
if (contributionBinding.factoryCreationStrategy().equals(ENUM_INSTANCE)
&& !contributionBinding.scope().isPresent()) {
return Optional.of(
staticSelect(
generatedClassNameForBinding(contributionBinding), Snippet.format("create()")));
}
break;
case MEMBERS_INJECTION:
Optional<MembersInjectionBinding> membersInjectionBinding =
resolvedBindings.membersInjectionBinding();
if (membersInjectionBinding.isPresent()
&& membersInjectionBinding.get().injectionStrategy().equals(NO_OP)) {
return Optional.of(
staticMethodInvocationWithCast(
ClassName.fromClass(MembersInjectors.class),
Snippet.format("noOp()"),
ClassName.fromClass(MembersInjector.class)));
}
break;
default:
throw new AssertionError();
}
return Optional.absent();
}
private void implementInterfaceMethods() {
Set<MethodSignature> interfaceMethods = Sets.newHashSet();
for (ComponentMethodDescriptor componentMethod :
graph.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(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();
MemberSelect memberSelect = getMemberSelect(bindingKey);
Snippet memberSelectSnippet = memberSelect.getSnippetFor(name);
switch (interfaceRequest.kind()) {
case MEMBERS_INJECTOR:
List<? extends VariableElement> parameters = requestElement.getParameters();
if (parameters.isEmpty()) {
// we're returning the framework type
interfaceMethod.body().addSnippet("return %s;", memberSelectSnippet);
} 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);",
memberSelectSnippet,
parameterName);
if (!requestType.getReturnType().getKind().equals(VOID)) {
interfaceMethod.body().addSnippet("return %s;", parameterName);
}
}
break;
case INSTANCE:
if (memberSelect.staticMember()
&& 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, memberSelectSnippet);
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(
memberSelectSnippet, interfaceRequest.kind()));
break;
default:
throw new AssertionError();
}
}
}
}
}
private void addSubcomponents() {
for (Map.Entry<ExecutableElement, BindingGraph> subgraphEntry : graph.subgraphs().entrySet()) {
SubcomponentWriter subcomponent =
new SubcomponentWriter(this, subgraphEntry.getKey(), subgraphEntry.getValue());
javaWriters.addAll(subcomponent.write());
}
}
private static final int SNIPPETS_PER_INITIALIZATION_METHOD = 100;
private void initializeFrameworkTypes() {
ImmutableList.Builder<Snippet> snippetsBuilder = ImmutableList.builder();
for (BindingKey bindingKey : graph.resolvedBindings().keySet()) {
snippetsBuilder.add(initializeFrameworkType(bindingKey));
}
ImmutableList<Snippet> snippets = snippetsBuilder.build();
List<List<Snippet>> partitions = Lists.partition(snippets, SNIPPETS_PER_INITIALIZATION_METHOD);
for (int i = 0; i < partitions.size(); i++) {
MethodWriter initializeMethod =
componentWriter.addMethod(VoidName.VOID, "initialize" + ((i == 0) ? "" : i));
/* TODO(gak): Strictly speaking, we only need the suppression here if we are also initializing
* a raw field in this method, but the structure of this code makes it awkward to pass that
* bit through. This will be cleaned up when we no longer separate fields and initilization
* as we do now. */
initializeMethod.annotate(SuppressWarnings.class).setValue("unchecked");
for (Snippet snippet : partitions.get(i)) {
initializeMethod.body().addSnippet(snippet);
}
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());
}
}
}
/**
* Returns a single snippet representing the initialization of the framework type.
*
* <p>Note that this must be a single snippet because initialization snippets can be invoked from
* any place in any order. By requiring a single snippet (often of concatenated snippets) we
* ensure that things like local variables always behave as expected by the initialization logic.
*/
private Snippet initializeFrameworkType(BindingKey bindingKey) {
ResolvedBindings resolvedBindings = graph.resolvedBindings().get(bindingKey);
// There's no field for inherited bindings.
if (resolvedBindings.ownedBindings().isEmpty()) {
return Snippet.format("");
}
switch (bindingKey.kind()) {
case CONTRIBUTION:
switch (contributionTypeFor(resolvedBindings.contributionBindings())) {
case SET:
return initializeSetMultibindings(resolvedBindings);
case MAP:
return initializeMapMultibindings(resolvedBindings);
case UNIQUE:
return initializeUniqueContributionBinding(resolvedBindings);
default:
throw new AssertionError();
}
case MEMBERS_INJECTION:
return initializeMembersInjectionBinding(resolvedBindings);
default:
throw new AssertionError();
}
}
private Snippet initializeSetMultibindings(ResolvedBindings resolvedBindings) {
ImmutableList.Builder<Snippet> initializationSnippets = ImmutableList.builder();
ImmutableList.Builder<Snippet> parameterSnippets = ImmutableList.builder();
for (ContributionBinding binding : resolvedBindings.contributionBindings()) {
Optional<MemberSelect> multibindingContributionSnippet =
getMultibindingContributionSnippet(binding);
checkState(multibindingContributionSnippet.isPresent(), "%s was not found", binding);
Snippet snippet = multibindingContributionSnippet.get().getSnippetFor(name);
if (multibindingContributionSnippet.get().owningClass().equals(name)
// the binding might already be initialized by a different set binding that shares the
// same contributions (e.g., Set<T> and Set<Produced<T>>)
&& getContributionInitializationState(binding)
.equals(InitializationState.UNINITIALIZED)) {
Snippet initializeSnippet = initializeFactoryForContributionBinding(binding);
initializationSnippets.add(Snippet.format("this.%s = %s;", snippet, initializeSnippet));
setContributionInitializationState(binding, InitializationState.INITIALIZED);
}
parameterSnippets.add(snippet);
}
Class<?> factoryClass =
Iterables.all(resolvedBindings.contributionBindings(), Binding.Type.PROVISION)
? SetFactory.class
: Util.isSetOfProduced(resolvedBindings.bindingKey().key().type())
? SetOfProducedProducer.class
: SetProducer.class;
Snippet initializeSetSnippet =
Snippet.format(
"%s.create(%s)",
ClassName.fromClass(factoryClass),
makeParametersSnippet(parameterSnippets.build()));
initializationSnippets.add(
initializeMember(resolvedBindings.bindingKey(), initializeSetSnippet));
return Snippet.concat(initializationSnippets.build());
}
private Snippet initializeMapMultibindings(ResolvedBindings resolvedBindings) {
ImmutableList.Builder<Snippet> initializationSnippets = ImmutableList.builder();
if (any(resolvedBindings.contributionBindings(), Binding.Type.PRODUCTION)) {
// TODO(beder): Implement producer map bindings.
throw new IllegalStateException("producer map bindings not implemented yet");
}
for (ContributionBinding binding : resolvedBindings.contributionBindings()) {
Optional<MemberSelect> multibindingContributionSnippet =
getMultibindingContributionSnippet(binding);
if (!isMapWithNonProvidedValues(binding.key().type())
&& multibindingContributionSnippet.isPresent()
&& multibindingContributionSnippet.get().owningClass().equals(name)) {
initializationSnippets.add(
Snippet.format(
"this.%s = %s;",
multibindingContributionSnippet.get().getSnippetFor(name),
initializeFactoryForContributionBinding(binding)));
}
}
initializationSnippets.add(
initializeMember(
resolvedBindings.bindingKey(),
initializeMapBinding(resolvedBindings.contributionBindings())));
return Snippet.concat(initializationSnippets.build());
}
private Snippet initializeUniqueContributionBinding(ResolvedBindings resolvedBindings) {
ImmutableList.Builder<Snippet> initializationSnippets = ImmutableList.builder();
ContributionBinding binding = getOnlyElement(resolvedBindings.ownedContributionBindings());
if (!binding.factoryCreationStrategy().equals(ENUM_INSTANCE) || binding.scope().isPresent()) {
initializationSnippets.add(initializeDelegateFactories(binding));
initializationSnippets.add(
initializeMember(
resolvedBindings.bindingKey(), initializeFactoryForContributionBinding(binding)));
}
return Snippet.concat(initializationSnippets.build());
}
private Snippet initializeMembersInjectionBinding(ResolvedBindings resolvedBindings) {
ImmutableList.Builder<Snippet> initializationSnippets = ImmutableList.builder();
MembersInjectionBinding binding = resolvedBindings.membersInjectionBinding().get();
if (!binding.injectionStrategy().equals(MembersInjectionBinding.Strategy.NO_OP)) {
initializationSnippets.add(initializeDelegateFactories(binding));
initializationSnippets.add(
initializeMember(
resolvedBindings.bindingKey(), initializeMembersInjectorForBinding(binding)));
}
return Snippet.concat(initializationSnippets.build());
}
private Snippet initializeDelegateFactories(Binding binding) {
ImmutableList.Builder<Snippet> initializationSnippets = ImmutableList.builder();
for (Collection<DependencyRequest> requestsForKey :
indexDependenciesByUnresolvedKey(types, binding.dependencies()).asMap().values()) {
BindingKey dependencyKey =
Iterables.getOnlyElement(
FluentIterable.from(requestsForKey)
.transform(DependencyRequest.BINDING_KEY_FUNCTION)
.toSet());
if (!getMemberSelect(dependencyKey).staticMember()
&& getInitializationState(dependencyKey).equals(UNINITIALIZED)) {
initializationSnippets.add(
Snippet.format(
"this.%s = new %s();",
getMemberSelectSnippet(dependencyKey),
ClassName.fromClass(DelegateFactory.class)));
setInitializationState(dependencyKey, DELEGATED);
}
}
return Snippet.concat(initializationSnippets.build());
}
private Snippet initializeMember(BindingKey bindingKey, Snippet initializationSnippet) {
ImmutableList.Builder<Snippet> initializationSnippets = ImmutableList.builder();
Snippet memberSelect = getMemberSelectSnippet(bindingKey);
Snippet delegateFactoryVariable = delegateFactoryVariableSnippet(bindingKey);
if (getInitializationState(bindingKey).equals(DELEGATED)) {
initializationSnippets.add(
Snippet.format(
"%1$s %2$s = (%1$s) %3$s;",
ClassName.fromClass(DelegateFactory.class),
delegateFactoryVariable,
memberSelect));
}
initializationSnippets.add(
Snippet.format("this.%s = %s;", memberSelect, initializationSnippet));
if (getInitializationState(bindingKey).equals(DELEGATED)) {
initializationSnippets.add(
Snippet.format("%s.setDelegatedProvider(%s);", delegateFactoryVariable, memberSelect));
}
setInitializationState(bindingKey, INITIALIZED);
return Snippet.concat(initializationSnippets.build());
}
private Snippet delegateFactoryVariableSnippet(BindingKey key) {
return Snippet.format("%sDelegate", getMemberSelectSnippet(key).toString().replace('.', '_'));
}
private Snippet initializeFactoryForContributionBinding(ContributionBinding binding) {
TypeName bindingKeyTypeName = TypeNames.forTypeMirror(binding.key().type());
switch (binding.bindingKind()) {
case COMPONENT:
return Snippet.format(
"%s.<%s>create(%s)",
ClassName.fromClass(InstanceFactory.class),
bindingKeyTypeName,
bindingKeyTypeName.equals(componentDefinitionTypeName())
? "this"
: getComponentContributionSnippet(MoreTypes.asTypeElement(binding.key().type())));
case COMPONENT_PROVISION:
{
TypeElement bindingTypeElement =
graph.componentDescriptor().dependencyMethodIndex().get(binding.bindingElement());
String localFactoryVariable = simpleVariableName(bindingTypeElement);
Snippet callFactoryMethodSnippet =
Snippet.format(
"%s.%s()",
localFactoryVariable,
binding.bindingElement().getSimpleName().toString());
// 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);
Snippet getMethodBody =
binding.nullableType().isPresent()
|| nullableValidationType.equals(Diagnostic.Kind.WARNING)
? Snippet.format("return %s;", callFactoryMethodSnippet)
: Snippet.format(
Joiner.on('\n')
.join(
"%s provided = %s;",
"if (provided == null) {",
" throw new NullPointerException(%s);",
"}",
"return provided;"),
bindingKeyTypeName,
callFactoryMethodSnippet,
failMsg);
return Snippet.format(
Joiner.on('\n')
.join(
"new %1$s<%2$s>() {",
" private final %5$s %6$s = %3$s;",
" %4$s@Override public %2$s get() {",
" %7$s",
" }",
"}"),
/* 1 */ ClassName.fromClass(Factory.class),
/* 2 */ bindingKeyTypeName,
/* 3 */ getComponentContributionSnippet(bindingTypeElement),
/* 4 */ nullableSnippet(binding.nullableType()),
/* 5 */ TypeNames.forTypeMirror(bindingTypeElement.asType()),
/* 6 */ localFactoryVariable,
/* 7 */ getMethodBody);
}
case SUBCOMPONENT_BUILDER:
return Snippet.format(
Joiner.on('\n')
.join(
"new %1$s<%2$s>() {",
" @Override public %2$s get() {",
" return %3$s();",
" }",
"}"),
/* 1 */ ClassName.fromClass(Factory.class),
/* 2 */ bindingKeyTypeName,
/* 3 */ binding.bindingElement().getSimpleName().toString());
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(getComponentContributionSnippet(binding.contributedBy().get()));
}
parameters.addAll(getDependencyParameters(binding));
Snippet factorySnippet =
Snippet.format(
"%s.create(%s)",
generatedClassNameForBinding(binding),
Snippet.makeParametersSnippet(parameters));
return binding.scope().isPresent()
? Snippet.format(
"%s.create(%s)", ClassName.fromClass(ScopedProvider.class), factorySnippet)
: factorySnippet;
}
case COMPONENT_PRODUCTION:
{
TypeElement bindingTypeElement =
graph.componentDescriptor().dependencyMethodIndex().get(binding.bindingElement());
return Snippet.format(
Joiner.on('\n')
.join(
"new %1$s<%2$s>() {",
" private final %6$s %7$s = %4$s;",
" @Override public %3$s<%2$s> get() {",
" return %7$s.%5$s();",
" }",
"}"),
/* 1 */ ClassName.fromClass(Producer.class),
/* 2 */ TypeNames.forTypeMirror(binding.key().type()),
/* 3 */ ClassName.fromClass(ListenableFuture.class),
/* 4 */ getComponentContributionSnippet(bindingTypeElement),
/* 5 */ binding.bindingElement().getSimpleName().toString(),
/* 6 */ TypeNames.forTypeMirror(bindingTypeElement.asType()),
/* 7 */ simpleVariableName(bindingTypeElement));
}
case IMMEDIATE:
case FUTURE_PRODUCTION:
{
List<Snippet> parameters =
Lists.newArrayListWithCapacity(binding.implicitDependencies().size() + 2);
if (!binding.bindingElement().getModifiers().contains(STATIC)) {
parameters.add(getComponentContributionSnippet(binding.bindingTypeElement()));
}
parameters.add(
getComponentContributionSnippet(
graph.componentDescriptor().executorDependency().get()));
parameters.addAll(getProducerDependencyParameters(binding));
return Snippet.format(
"new %s(%s)",
generatedClassNameForBinding(binding),
Snippet.makeParametersSnippet(parameters));
}
default:
throw new AssertionError();
}
}
private Snippet nullableSnippet(Optional<DeclaredType> nullableType) {
return nullableType.isPresent()
? Snippet.format("@%s ", TypeNames.forTypeMirror(nullableType.get()))
: Snippet.format("");
}
private Snippet initializeMembersInjectorForBinding(MembersInjectionBinding binding) {
switch (binding.injectionStrategy()) {
case NO_OP:
return Snippet.format("%s.noOp()", ClassName.fromClass(MembersInjectors.class));
case INJECT_MEMBERS:
List<Snippet> parameters = getDependencyParameters(binding);
return Snippet.format(
"%s.create(%s)",
membersInjectorNameForType(binding.bindingElement()),
Snippet.makeParametersSnippet(parameters));
default:
throw new AssertionError();
}
}
private List<Snippet> getDependencyParameters(Binding binding) {
ImmutableList.Builder<Snippet> parameters = ImmutableList.builder();
Set<Key> keysSeen = new HashSet<>();
for (Collection<DependencyRequest> requestsForKey :
indexDependenciesByUnresolvedKey(types, binding.implicitDependencies()).asMap().values()) {
Set<BindingKey> requestedBindingKeys = new HashSet<>();
for (DependencyRequest dependencyRequest : requestsForKey) {
Element requestElement = dependencyRequest.requestElement();
TypeMirror typeMirror = typeMirrorAsMemberOf(binding.bindingTypeElement(), requestElement);
Key key = keyFactory.forQualifiedType(dependencyRequest.key().qualifier(), typeMirror);
if (keysSeen.add(key)) {
requestedBindingKeys.add(dependencyRequest.bindingKey());
}
}
if (!requestedBindingKeys.isEmpty()) {
BindingKey key = Iterables.getOnlyElement(requestedBindingKeys);
parameters.add(getMemberSelect(key).getSnippetWithRawTypeCastFor(name));
}
}
return parameters.build();
}
// TODO(dpb): Investigate use of asMemberOf here. Why aren't the dependency requests already
// resolved?
private TypeMirror typeMirrorAsMemberOf(TypeElement bindingTypeElement, Element requestElement) {
TypeMirror requestType = requestElement.asType();
if (requestType.getKind() == TypeKind.TYPEVAR) {
return types.asMemberOf(
MoreTypes.asDeclared(bindingTypeElement.asType()),
(requestElement.getKind() == ElementKind.PARAMETER)
? MoreTypes.asElement(requestType)
: requestElement);
} else {
return requestType;
}
}
private List<Snippet> getProducerDependencyParameters(Binding binding) {
ImmutableList.Builder<Snippet> parameters = ImmutableList.builder();
for (Collection<DependencyRequest> requestsForKey :
SourceFiles.indexDependenciesByUnresolvedKey(types, binding.implicitDependencies())
.asMap()
.values()) {
BindingKey key = Iterables.getOnlyElement(FluentIterable.from(requestsForKey)
.transform(DependencyRequest.BINDING_KEY_FUNCTION));
ResolvedBindings resolvedBindings = graph.resolvedBindings().get(key);
Class<?> frameworkClass =
DependencyRequestMapper.FOR_PRODUCER.getFrameworkClass(requestsForKey);
if (FrameworkField.frameworkClassForResolvedBindings(resolvedBindings).equals(Provider.class)
&& frameworkClass.equals(Producer.class)) {
parameters.add(
Snippet.format(
"%s.producerFromProvider(%s)",
ClassName.fromClass(Producers.class),
getMemberSelectSnippet(key)));
} else {
parameters.add(getMemberSelectSnippet(key));
}
}
return parameters.build();
}
private Snippet initializeMapBinding(Set<ContributionBinding> bindings) {
// Get type information from the first binding.
ContributionBinding firstBinding = bindings.iterator().next();
DeclaredType mapType = asDeclared(firstBinding.key().type());
if (isMapWithNonProvidedValues(mapType)) {
return Snippet.format(
"%s.create(%s)",
ClassName.fromClass(MapFactory.class),
getMemberSelectSnippet(getOnlyElement(firstBinding.dependencies()).bindingKey()));
}
ImmutableList.Builder<dagger.internal.codegen.writer.Snippet> snippets =
ImmutableList.builder();
snippets.add(Snippet.format("%s.<%s, %s>builder(%d)",
ClassName.fromClass(MapProviderFactory.class),
TypeNames.forTypeMirror(getKeyTypeOfMap(mapType)),
TypeNames.forTypeMirror(getProvidedValueTypeOfMap(mapType)), // V of Map<K, Provider<V>>
bindings.size()));
for (ContributionBinding binding : bindings) {
snippets.add(
Snippet.format(
" .put(%s, %s)",
getMapKeySnippet(binding.bindingElement()),
getMultibindingContributionSnippet(binding).get().getSnippetFor(name)));
}
snippets.add(Snippet.format(" .build()"));
return Snippet.concat(snippets.build());
}
private static String simpleVariableName(TypeElement typeElement) {
return UPPER_CAMEL.to(LOWER_CAMEL, typeElement.getSimpleName().toString());
}
/**
* Initialization state for a factory field.
*/
enum InitializationState {
/** The field is {@code null}. */
UNINITIALIZED,
/** The field is set to a {@link DelegateFactory}. */
DELEGATED,
/** The field is set to an undelegated factory. */
INITIALIZED;
}
}