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