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