• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.processing.XTypeKt.isVoid;
20 import static com.google.common.base.Preconditions.checkArgument;
21 import static com.google.common.base.Preconditions.checkNotNull;
22 import static com.google.common.collect.Iterables.getOnlyElement;
23 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
24 import static dagger.internal.codegen.binding.BindingRequest.bindingRequest;
25 import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock;
26 import static dagger.internal.codegen.langmodel.Accessibility.isRawTypeAccessible;
27 import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom;
28 import static dagger.internal.codegen.xprocessing.MethodSpecs.overriding;
29 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
30 import static dagger.internal.codegen.xprocessing.XProcessingEnvs.isPreJava8SourceVersion;
31 
32 import androidx.room.compiler.processing.XMethodElement;
33 import androidx.room.compiler.processing.XProcessingEnv;
34 import androidx.room.compiler.processing.XType;
35 import com.google.common.collect.ImmutableList;
36 import com.squareup.javapoet.ClassName;
37 import com.squareup.javapoet.CodeBlock;
38 import com.squareup.javapoet.MethodSpec;
39 import dagger.internal.codegen.base.MapType;
40 import dagger.internal.codegen.base.OptionalType;
41 import dagger.internal.codegen.binding.Binding;
42 import dagger.internal.codegen.binding.BindingGraph;
43 import dagger.internal.codegen.binding.BindingRequest;
44 import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor;
45 import dagger.internal.codegen.binding.ComponentRequirement;
46 import dagger.internal.codegen.binding.ContributionBinding;
47 import dagger.internal.codegen.binding.FrameworkType;
48 import dagger.internal.codegen.binding.FrameworkTypeMapper;
49 import dagger.internal.codegen.binding.MembersInjectionBinding;
50 import dagger.internal.codegen.binding.ProductionBinding;
51 import dagger.internal.codegen.binding.ProvisionBinding;
52 import dagger.internal.codegen.javapoet.Expression;
53 import dagger.internal.codegen.model.DependencyRequest;
54 import dagger.internal.codegen.model.RequestKind;
55 import java.util.HashMap;
56 import java.util.Map;
57 import java.util.Optional;
58 import javax.inject.Inject;
59 
60 /** A central repository of code expressions used to access any binding available to a component. */
61 @PerComponentImplementation
62 public final class ComponentRequestRepresentations {
63   // TODO(dpb,ronshapiro): refactor this and ComponentRequirementExpressions into a
64   // HierarchicalComponentMap<K, V>, or perhaps this use a flattened ImmutableMap, built from its
65   // parents? If so, maybe make RequestRepresentation.Factory create it.
66 
67   private final Optional<ComponentRequestRepresentations> parent;
68   private final BindingGraph graph;
69   private final ComponentImplementation componentImplementation;
70   private final ComponentRequirementExpressions componentRequirementExpressions;
71   private final MembersInjectionBindingRepresentation.Factory
72       membersInjectionBindingRepresentationFactory;
73   private final ProvisionBindingRepresentation.Factory provisionBindingRepresentationFactory;
74   private final ProductionBindingRepresentation.Factory productionBindingRepresentationFactory;
75   private final Map<Binding, BindingRepresentation> representations = new HashMap<>();
76   private final XProcessingEnv processingEnv;
77 
78   @Inject
ComponentRequestRepresentations( @arentComponent Optional<ComponentRequestRepresentations> parent, BindingGraph graph, ComponentImplementation componentImplementation, ComponentRequirementExpressions componentRequirementExpressions, MembersInjectionBindingRepresentation.Factory membersInjectionBindingRepresentationFactory, ProvisionBindingRepresentation.Factory provisionBindingRepresentationFactory, ProductionBindingRepresentation.Factory productionBindingRepresentationFactory, XProcessingEnv processingEnv)79   ComponentRequestRepresentations(
80       @ParentComponent Optional<ComponentRequestRepresentations> parent,
81       BindingGraph graph,
82       ComponentImplementation componentImplementation,
83       ComponentRequirementExpressions componentRequirementExpressions,
84       MembersInjectionBindingRepresentation.Factory membersInjectionBindingRepresentationFactory,
85       ProvisionBindingRepresentation.Factory provisionBindingRepresentationFactory,
86       ProductionBindingRepresentation.Factory productionBindingRepresentationFactory,
87       XProcessingEnv processingEnv) {
88     this.parent = parent;
89     this.graph = graph;
90     this.componentImplementation = componentImplementation;
91     this.membersInjectionBindingRepresentationFactory =
92         membersInjectionBindingRepresentationFactory;
93     this.provisionBindingRepresentationFactory = provisionBindingRepresentationFactory;
94     this.productionBindingRepresentationFactory = productionBindingRepresentationFactory;
95     this.componentRequirementExpressions = checkNotNull(componentRequirementExpressions);
96     this.processingEnv = processingEnv;
97   }
98 
99   /**
100    * Returns an expression that evaluates to the value of a binding request for a binding owned by
101    * this component or an ancestor.
102    *
103    * @param requestingClass the class that will contain the expression
104    * @throws IllegalStateException if there is no binding expression that satisfies the request
105    */
getDependencyExpression(BindingRequest request, ClassName requestingClass)106   public Expression getDependencyExpression(BindingRequest request, ClassName requestingClass) {
107     return getRequestRepresentation(request).getDependencyExpression(requestingClass);
108   }
109 
110   /**
111    * Equivalent to {@link #getDependencyExpression(BindingRequest, ClassName)} that is used only
112    * when the request is for implementation of a component method.
113    *
114    * @throws IllegalStateException if there is no binding expression that satisfies the request
115    */
getDependencyExpressionForComponentMethod( BindingRequest request, ComponentMethodDescriptor componentMethod, ComponentImplementation componentImplementation)116   Expression getDependencyExpressionForComponentMethod(
117       BindingRequest request,
118       ComponentMethodDescriptor componentMethod,
119       ComponentImplementation componentImplementation) {
120     return getRequestRepresentation(request)
121         .getDependencyExpressionForComponentMethod(componentMethod, componentImplementation);
122   }
123 
124   /**
125    * Returns the {@link CodeBlock} for the method arguments used with the factory {@code create()}
126    * method for the given {@link ContributionBinding binding}.
127    */
getCreateMethodArgumentsCodeBlock( ContributionBinding binding, ClassName requestingClass)128   CodeBlock getCreateMethodArgumentsCodeBlock(
129       ContributionBinding binding, ClassName requestingClass) {
130     return makeParametersCodeBlock(getCreateMethodArgumentsCodeBlocks(binding, requestingClass));
131   }
132 
getCreateMethodArgumentsCodeBlocks( ContributionBinding binding, ClassName requestingClass)133   private ImmutableList<CodeBlock> getCreateMethodArgumentsCodeBlocks(
134       ContributionBinding binding, ClassName requestingClass) {
135     ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder();
136 
137     if (binding.requiresModuleInstance()) {
138       arguments.add(
139           componentRequirementExpressions.getExpressionDuringInitialization(
140               ComponentRequirement.forModule(binding.contributingModule().get().getType()),
141               requestingClass));
142     }
143 
144     binding.dependencies().stream()
145         .map(dependency -> frameworkRequest(binding, dependency))
146         .map(request -> getDependencyExpression(request, requestingClass))
147         .map(Expression::codeBlock)
148         .forEach(arguments::add);
149 
150     return arguments.build();
151   }
152 
frameworkRequest( ContributionBinding binding, DependencyRequest dependency)153   private static BindingRequest frameworkRequest(
154       ContributionBinding binding, DependencyRequest dependency) {
155     // TODO(bcorso): See if we can get rid of FrameworkTypeMatcher
156     FrameworkType frameworkType =
157         FrameworkTypeMapper.forBindingType(binding.bindingType())
158             .getFrameworkType(dependency.kind());
159     return BindingRequest.bindingRequest(dependency.key(), frameworkType);
160   }
161 
162   /**
163    * Returns an expression that evaluates to the value of a dependency request, for passing to a
164    * binding method, an {@code @Inject}-annotated constructor or member, or a proxy for one.
165    *
166    * <p>If the method is a generated static {@link InjectionMethods injection method}, each
167    * parameter will be {@link Object} if the dependency's raw type is inaccessible. If that is the
168    * case for this dependency, the returned expression will use a cast to evaluate to the raw type.
169    *
170    * @param requestingClass the class that will contain the expression
171    */
getDependencyArgumentExpression( DependencyRequest dependencyRequest, ClassName requestingClass)172   Expression getDependencyArgumentExpression(
173       DependencyRequest dependencyRequest, ClassName requestingClass) {
174 
175     XType dependencyType = dependencyRequest.key().type().xprocessing();
176     BindingRequest bindingRequest = bindingRequest(dependencyRequest);
177     Expression dependencyExpression = getDependencyExpression(bindingRequest, requestingClass);
178 
179     if (dependencyRequest.kind().equals(RequestKind.INSTANCE)
180         && !isTypeAccessibleFrom(dependencyType, requestingClass.packageName())
181         && isRawTypeAccessible(dependencyType, requestingClass.packageName())) {
182       return dependencyExpression.castTo(dependencyType.getRawType());
183     }
184 
185     return dependencyExpression;
186   }
187 
188   /** Returns the implementation of a component method. */
getComponentMethod(ComponentMethodDescriptor componentMethod)189   public MethodSpec getComponentMethod(ComponentMethodDescriptor componentMethod) {
190     checkArgument(componentMethod.dependencyRequest().isPresent());
191     BindingRequest request = bindingRequest(componentMethod.dependencyRequest().get());
192     return overriding(componentMethod.methodElement(), graph.componentTypeElement().getType())
193         .addCode(
194             request.isRequestKind(RequestKind.MEMBERS_INJECTION)
195                 ? getMembersInjectionComponentMethodImplementation(request, componentMethod)
196                 : getContributionComponentMethodImplementation(request, componentMethod))
197         .build();
198   }
199 
getMembersInjectionComponentMethodImplementation( BindingRequest request, ComponentMethodDescriptor componentMethod)200   private CodeBlock getMembersInjectionComponentMethodImplementation(
201       BindingRequest request, ComponentMethodDescriptor componentMethod) {
202     checkArgument(request.isRequestKind(RequestKind.MEMBERS_INJECTION));
203     XMethodElement methodElement = componentMethod.methodElement();
204     RequestRepresentation requestRepresentation = getRequestRepresentation(request);
205     MembersInjectionBinding binding =
206         ((MembersInjectionRequestRepresentation) requestRepresentation).binding();
207     if (binding.injectionSites().isEmpty()) {
208       // If there are no injection sites either do nothing (if the return type is void) or return
209       // the input instance as-is.
210       return isVoid(methodElement.getReturnType())
211           ? CodeBlock.of("")
212           : CodeBlock.of(
213               "return $L;", getSimpleName(getOnlyElement(methodElement.getParameters())));
214     }
215     Expression expression = getComponentMethodExpression(requestRepresentation, componentMethod);
216     return isVoid(methodElement.getReturnType())
217         ? CodeBlock.of("$L;", expression.codeBlock())
218         : CodeBlock.of("return $L;", expression.codeBlock());
219   }
220 
getContributionComponentMethodImplementation( BindingRequest request, ComponentMethodDescriptor componentMethod)221   private CodeBlock getContributionComponentMethodImplementation(
222       BindingRequest request, ComponentMethodDescriptor componentMethod) {
223     checkArgument(!request.isRequestKind(RequestKind.MEMBERS_INJECTION));
224     Expression expression =
225         getComponentMethodExpression(getRequestRepresentation(request), componentMethod);
226     return CodeBlock.of("return $L;", expression.codeBlock());
227   }
228 
getComponentMethodExpression( RequestRepresentation requestRepresentation, ComponentMethodDescriptor componentMethod)229   private Expression getComponentMethodExpression(
230       RequestRepresentation requestRepresentation, ComponentMethodDescriptor componentMethod) {
231     Expression expression =
232         requestRepresentation.getDependencyExpressionForComponentMethod(
233             componentMethod, componentImplementation);
234 
235     // Cast if the expression type does not match the component method's return type. This is useful
236     // for types that have protected accessibility to the component but are not accessible to other
237     // classes, e.g. shards, that may need to handle the implementation of the binding.
238     XType returnType =
239         componentMethod.methodElement()
240             .asMemberOf(componentImplementation.graph().componentTypeElement().getType())
241             .getReturnType();
242 
243     // When compiling with -source 7, javac's type inference isn't strong enough to match things
244     // like Optional<javax.inject.Provider<T>> to Optional<dagger.internal.Provider<T>>.
245     if (isPreJava8SourceVersion(processingEnv)
246         && (MapType.isMapOfProvider(returnType)
247             || OptionalType.isOptionalProviderType(returnType))) {
248       return expression.castTo(returnType.getRawType());
249     }
250 
251     return !isVoid(returnType) && !expression.type().isAssignableTo(returnType)
252         ? expression.castTo(returnType)
253         : expression;
254   }
255 
256   /** Returns the {@link RequestRepresentation} for the given {@link BindingRequest}. */
getRequestRepresentation(BindingRequest request)257   RequestRepresentation getRequestRepresentation(BindingRequest request) {
258     Optional<Binding> localBinding =
259         request.isRequestKind(RequestKind.MEMBERS_INJECTION)
260             ? graph.localMembersInjectionBinding(request.key())
261             : graph.localContributionBinding(request.key());
262 
263     if (localBinding.isPresent()) {
264       return getBindingRepresentation(localBinding.get()).getRequestRepresentation(request);
265     }
266 
267     checkArgument(parent.isPresent(), "no expression found for %s", request);
268     return parent.get().getRequestRepresentation(request);
269   }
270 
getBindingRepresentation(Binding binding)271   private BindingRepresentation getBindingRepresentation(Binding binding) {
272     return reentrantComputeIfAbsent(
273         representations, binding, this::getBindingRepresentationUncached);
274   }
275 
getBindingRepresentationUncached(Binding binding)276   private BindingRepresentation getBindingRepresentationUncached(Binding binding) {
277     switch (binding.bindingType()) {
278       case MEMBERS_INJECTION:
279         return membersInjectionBindingRepresentationFactory.create(
280             (MembersInjectionBinding) binding);
281       case PROVISION:
282         return provisionBindingRepresentationFactory.create((ProvisionBinding) binding);
283       case PRODUCTION:
284         return productionBindingRepresentationFactory.create((ProductionBinding) binding);
285     }
286     throw new AssertionError();
287   }
288 }
289