/* * 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 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 instead of Provider>). 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; } }