• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.internal.codegen.writing;
18 
19 import static androidx.room.compiler.codegen.XTypeNameKt.toJavaPoet;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding;
22 import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES;
23 import static dagger.internal.codegen.writing.ComponentImplementation.FieldSpecKind.FRAMEWORK_FIELD;
24 import static javax.lang.model.element.Modifier.PRIVATE;
25 
26 import androidx.room.compiler.codegen.XClassName;
27 import androidx.room.compiler.codegen.XTypeName;
28 import androidx.room.compiler.processing.XType;
29 import com.squareup.javapoet.ClassName;
30 import com.squareup.javapoet.CodeBlock;
31 import com.squareup.javapoet.FieldSpec;
32 import dagger.internal.DelegateFactory;
33 import dagger.internal.codegen.binding.BindingType;
34 import dagger.internal.codegen.binding.ContributionBinding;
35 import dagger.internal.codegen.binding.FrameworkField;
36 import dagger.internal.codegen.javapoet.AnnotationSpecs;
37 import dagger.internal.codegen.javapoet.TypeNames;
38 import dagger.internal.codegen.model.BindingKind;
39 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
40 import dagger.internal.codegen.xprocessing.XTypeNames;
41 import java.util.Optional;
42 
43 /**
44  * An object that can initialize a framework-type component field for a binding. An instance should
45  * be created for each field.
46  */
47 class FrameworkFieldInitializer implements FrameworkInstanceSupplier {
48 
49   /**
50    * An object that can determine the expression to use to assign to the component field for a
51    * binding.
52    */
53   interface FrameworkInstanceCreationExpression {
54     /** Returns the expression to use to assign to the component field for the binding. */
creationExpression()55     CodeBlock creationExpression();
56 
57     /**
58      * Returns the framework class to use for the field, if different from the one implied by the
59      * binding. This implementation returns {@link Optional#empty()}.
60      */
alternativeFrameworkClass()61     default Optional<XClassName> alternativeFrameworkClass() {
62       return Optional.empty();
63     }
64   }
65 
66   private final ShardImplementation shardImplementation;
67   private final ContributionBinding binding;
68   private final FrameworkInstanceCreationExpression frameworkInstanceCreationExpression;
69   private FieldSpec fieldSpec;
70   private InitializationState fieldInitializationState = InitializationState.UNINITIALIZED;
71 
FrameworkFieldInitializer( ComponentImplementation componentImplementation, ContributionBinding binding, FrameworkInstanceCreationExpression frameworkInstanceCreationExpression)72   FrameworkFieldInitializer(
73       ComponentImplementation componentImplementation,
74       ContributionBinding binding,
75       FrameworkInstanceCreationExpression frameworkInstanceCreationExpression) {
76     this.binding = checkNotNull(binding);
77     this.shardImplementation = checkNotNull(componentImplementation).shardImplementation(binding);
78     this.frameworkInstanceCreationExpression = checkNotNull(frameworkInstanceCreationExpression);
79   }
80 
81   /**
82    * Returns the {@link MemberSelect} for the framework field, and adds the field and its
83    * initialization code to the component if it's needed and not already added.
84    */
85   @Override
memberSelect()86   public final MemberSelect memberSelect() {
87     initializeField();
88     return MemberSelect.localField(shardImplementation, checkNotNull(fieldSpec).name);
89   }
90 
91   /** Adds the field and its initialization code to the component. */
initializeField()92   private void initializeField() {
93     switch (fieldInitializationState) {
94       case UNINITIALIZED:
95         // Change our state in case we are recursively invoked via initializeRequestRepresentation
96         fieldInitializationState = InitializationState.INITIALIZING;
97         CodeBlock.Builder codeBuilder = CodeBlock.builder();
98         CodeBlock fieldInitialization = frameworkInstanceCreationExpression.creationExpression();
99         CodeBlock initCode = CodeBlock.of("this.$N = $L;", getOrCreateField(), fieldInitialization);
100 
101         if (fieldInitializationState == InitializationState.DELEGATED) {
102           codeBuilder.add(
103               "$T.setDelegate($N, $L);", delegateType(), fieldSpec, fieldInitialization);
104         } else {
105           codeBuilder.add(initCode);
106         }
107         shardImplementation.addInitialization(codeBuilder.build());
108 
109         fieldInitializationState = InitializationState.INITIALIZED;
110         break;
111 
112       case INITIALIZING:
113         fieldSpec = getOrCreateField();
114         // We were recursively invoked, so create a delegate factory instead to break the loop.
115 
116         // TODO(erichang): For the most part SwitchingProvider takes no dependencies so even if they
117         // are recursively invoked, we don't need to delegate it since there is no dependency cycle.
118         // However, there is a case with a scoped @Binds where we reference the impl binding when
119         // passing it into DoubleCheck. For this case, we do need to delegate it. There might be
120         // a way to only do delegates in this situation, but we'd need to keep track of what other
121         // bindings use this.
122 
123         fieldInitializationState = InitializationState.DELEGATED;
124         shardImplementation.addInitialization(
125             CodeBlock.of("this.$N = new $T<>();", fieldSpec, delegateType()));
126         break;
127 
128       case DELEGATED:
129       case INITIALIZED:
130         break;
131     }
132   }
133 
134   /**
135    * Adds a field representing the resolved bindings, optionally forcing it to use a particular
136    * binding type (instead of the type the resolved bindings would typically use).
137    */
getOrCreateField()138   private FieldSpec getOrCreateField() {
139     if (fieldSpec != null) {
140       return fieldSpec;
141     }
142     boolean useRawType = !shardImplementation.isTypeAccessible(binding.key().type().xprocessing());
143     FrameworkField contributionBindingField =
144         FrameworkField.forBinding(
145             binding, frameworkInstanceCreationExpression.alternativeFrameworkClass());
146 
147     XTypeName fieldType = useRawType
148         ? contributionBindingField.type().getRawTypeName()
149         : contributionBindingField.type();
150 
151     if (binding.kind() == BindingKind.ASSISTED_INJECTION) {
152       // An assisted injection factory doesn't extend Provider, so we reference the generated
153       // factory type directly (i.e. Foo_Factory<T> instead of Provider<Foo<T>>).
154       XTypeName[] typeParameters =
155           binding.key().type().xprocessing().getTypeArguments().stream()
156               .map(XType::asTypeName)
157               .toArray(XTypeName[]::new);
158       fieldType =
159           typeParameters.length == 0
160               ? generatedClassNameForBinding(binding)
161               : generatedClassNameForBinding(binding).parametrizedBy(typeParameters);
162     }
163 
164     FieldSpec.Builder contributionField =
165         FieldSpec.builder(
166             toJavaPoet(fieldType),
167             shardImplementation.getUniqueFieldName(contributionBindingField.name()));
168     contributionField.addModifiers(PRIVATE);
169     if (useRawType) {
170       contributionField.addAnnotation(AnnotationSpecs.suppressWarnings(RAWTYPES));
171     }
172 
173     fieldSpec = contributionField.build();
174     shardImplementation.addField(FRAMEWORK_FIELD, fieldSpec);
175 
176     return fieldSpec;
177   }
178 
delegateType()179   private ClassName delegateType() {
180     return isProvider() ? TypeNames.DELEGATE_FACTORY : TypeNames.DELEGATE_PRODUCER;
181   }
182 
isProvider()183   private boolean isProvider() {
184     return binding.bindingType().equals(BindingType.PROVISION)
185         && frameworkInstanceCreationExpression
186             .alternativeFrameworkClass()
187             .map(XTypeNames.PROVIDER::equals)
188             .orElse(true);
189   }
190 
191   /** Initialization state for a factory field. */
192   private enum InitializationState {
193     /** The field is {@code null}. */
194     UNINITIALIZED,
195 
196     /**
197      * The field's dependencies are being set up. If the field is needed in this state, use a {@link
198      * DelegateFactory}.
199      */
200     INITIALIZING,
201 
202     /**
203      * The field's dependencies are being set up, but the field can be used because it has already
204      * been set to a {@link DelegateFactory}.
205      */
206     DELEGATED,
207 
208     /** The field is set to an undelegated factory. */
209     INITIALIZED;
210   }
211 }
212