| /* |
| * 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; |
| } |
| } |