blob: c17ff8e53d9c4c6415492f90a8391673fd0db9ab [file] [log] [blame]
/*
* Copyright (C) 2015 The Dagger Authors.
*
* 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.writing;
import static com.google.common.base.Preconditions.checkNotNull;
import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding;
import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES;
import static dagger.internal.codegen.writing.ComponentImplementation.FieldSpecKind.FRAMEWORK_FIELD;
import static javax.lang.model.element.Modifier.PRIVATE;
import com.google.auto.common.MoreTypes;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import dagger.internal.DelegateFactory;
import dagger.internal.codegen.binding.BindingType;
import dagger.internal.codegen.binding.ContributionBinding;
import dagger.internal.codegen.binding.FrameworkField;
import dagger.internal.codegen.javapoet.AnnotationSpecs;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.model.BindingKind;
import dagger.producers.internal.DelegateProducer;
import java.util.Optional;
/**
* An object that can initialize a framework-type component field for a binding. An instance should
* be created for each field.
*/
class FrameworkFieldInitializer implements FrameworkInstanceSupplier {
/**
* An object that can determine the expression to use to assign to the component field for a
* binding.
*/
interface FrameworkInstanceCreationExpression {
/** Returns the expression to use to assign to the component field for the binding. */
CodeBlock creationExpression();
/**
* Returns the framework class to use for the field, if different from the one implied by the
* binding. This implementation returns {@link Optional#empty()}.
*/
default Optional<ClassName> alternativeFrameworkClass() {
return Optional.empty();
}
/**
* Returns {@code true} if instead of using {@link #creationExpression()} to create a framework
* instance, a case in {@link InnerSwitchingProviders} should be created for this binding.
*/
// TODO(ronshapiro): perhaps this isn't the right approach. Instead of saying "Use
// SetFactory.EMPTY because you will only get 1 class for all types of bindings that use
// SetFactory", maybe we should still use an inner switching provider but the same switching
// provider index for all cases.
default boolean useInnerSwitchingProvider() {
return true;
}
}
private final ComponentImplementation componentImplementation;
private final ContributionBinding binding;
private final FrameworkInstanceCreationExpression frameworkInstanceCreationExpression;
private FieldSpec fieldSpec;
private InitializationState fieldInitializationState = InitializationState.UNINITIALIZED;
FrameworkFieldInitializer(
ComponentImplementation componentImplementation,
ContributionBinding binding,
FrameworkInstanceCreationExpression frameworkInstanceCreationExpression) {
this.componentImplementation = checkNotNull(componentImplementation);
this.binding = checkNotNull(binding);
this.frameworkInstanceCreationExpression = checkNotNull(frameworkInstanceCreationExpression);
}
/**
* Returns the {@link MemberSelect} for the framework field, and adds the field and its
* initialization code to the component if it's needed and not already added.
*/
@Override
public final MemberSelect memberSelect() {
initializeField();
return MemberSelect.localField(componentImplementation.name(), checkNotNull(fieldSpec).name);
}
/** Adds the field and its initialization code to the component. */
private void initializeField() {
switch (fieldInitializationState) {
case UNINITIALIZED:
// Change our state in case we are recursively invoked via initializeBindingExpression
fieldInitializationState = InitializationState.INITIALIZING;
CodeBlock.Builder codeBuilder = CodeBlock.builder();
CodeBlock fieldInitialization = frameworkInstanceCreationExpression.creationExpression();
CodeBlock initCode = CodeBlock.of("this.$N = $L;", getOrCreateField(), fieldInitialization);
if (fieldInitializationState == InitializationState.DELEGATED) {
codeBuilder.add(
"$T.setDelegate($N, $L);", delegateType(), fieldSpec, fieldInitialization);
} else {
codeBuilder.add(initCode);
}
componentImplementation.addInitialization(codeBuilder.build());
fieldInitializationState = InitializationState.INITIALIZED;
break;
case INITIALIZING:
// We were recursively invoked, so create a delegate factory instead
fieldInitializationState = InitializationState.DELEGATED;
componentImplementation.addInitialization(
CodeBlock.of("this.$N = new $T<>();", getOrCreateField(), delegateType()));
break;
case DELEGATED:
case INITIALIZED:
break;
}
}
/**
* Adds a field representing the resolved bindings, optionally forcing it to use a particular
* binding type (instead of the type the resolved bindings would typically use).
*/
private FieldSpec getOrCreateField() {
if (fieldSpec != null) {
return fieldSpec;
}
boolean useRawType = !componentImplementation.isTypeAccessible(binding.key().type());
FrameworkField contributionBindingField =
FrameworkField.forBinding(
binding, frameworkInstanceCreationExpression.alternativeFrameworkClass());
TypeName fieldType =
useRawType ? contributionBindingField.type().rawType : contributionBindingField.type();
if (binding.kind() == BindingKind.ASSISTED_INJECTION) {
// An assisted injection factory doesn't extend Provider, so we reference the generated
// factory type directly (i.e. Foo_Factory<T> instead of Provider<Foo<T>>).
TypeName[] typeParameters =
MoreTypes.asDeclared(binding.key().type()).getTypeArguments().stream()
.map(TypeName::get)
.toArray(TypeName[]::new);
fieldType =
typeParameters.length == 0
? generatedClassNameForBinding(binding)
: ParameterizedTypeName.get(generatedClassNameForBinding(binding), typeParameters);
}
FieldSpec.Builder contributionField =
FieldSpec.builder(
fieldType, componentImplementation.getUniqueFieldName(contributionBindingField.name()));
contributionField.addModifiers(PRIVATE);
if (useRawType) {
contributionField.addAnnotation(AnnotationSpecs.suppressWarnings(RAWTYPES));
}
fieldSpec = contributionField.build();
componentImplementation.addField(FRAMEWORK_FIELD, fieldSpec);
return fieldSpec;
}
private Class<?> delegateType() {
return isProvider() ? DelegateFactory.class : DelegateProducer.class;
}
private boolean isProvider() {
return binding.bindingType().equals(BindingType.PROVISION)
&& frameworkInstanceCreationExpression
.alternativeFrameworkClass()
.map(TypeNames.PROVIDER::equals)
.orElse(true);
}
/** Initialization state for a factory field. */
private enum InitializationState {
/** The field is {@code null}. */
UNINITIALIZED,
/**
* The field's dependencies are being set up. If the field is needed in this state, use a {@link
* DelegateFactory}.
*/
INITIALIZING,
/**
* The field's dependencies are being set up, but the field can be used because it has already
* been set to a {@link DelegateFactory}.
*/
DELEGATED,
/** The field is set to an undelegated factory. */
INITIALIZED;
}
}