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 com.google.auto.common.MoreElements.asExecutable;
20 import static com.google.auto.common.MoreElements.asType;
21 import static com.google.common.base.Preconditions.checkArgument;
22 import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock;
23 import static dagger.internal.codegen.javapoet.TypeNames.rawTypeName;
24 import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom;
25 import static dagger.internal.codegen.writing.InjectionMethods.ProvisionMethod.requiresInjectionMethod;
26 
27 import com.google.auto.common.MoreTypes;
28 import com.squareup.javapoet.ClassName;
29 import com.squareup.javapoet.CodeBlock;
30 import com.squareup.javapoet.MethodSpec;
31 import com.squareup.javapoet.TypeName;
32 import dagger.internal.codegen.binding.ComponentRequirement;
33 import dagger.internal.codegen.binding.ProvisionBinding;
34 import dagger.internal.codegen.compileroption.CompilerOptions;
35 import dagger.internal.codegen.javapoet.Expression;
36 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
37 import dagger.internal.codegen.langmodel.DaggerElements;
38 import dagger.internal.codegen.writing.InjectionMethods.ProvisionMethod;
39 import dagger.model.DependencyRequest;
40 import java.util.Optional;
41 import javax.lang.model.SourceVersion;
42 import javax.lang.model.element.Element;
43 import javax.lang.model.element.ExecutableElement;
44 import javax.lang.model.type.DeclaredType;
45 import javax.lang.model.type.TypeMirror;
46 
47 /**
48  * A binding expression that invokes methods or constructors directly (without attempting to scope)
49  * {@link dagger.model.RequestKind#INSTANCE} requests.
50  */
51 final class SimpleMethodBindingExpression extends SimpleInvocationBindingExpression {
52   private final CompilerOptions compilerOptions;
53   private final ProvisionBinding provisionBinding;
54   private final ComponentBindingExpressions componentBindingExpressions;
55   private final MembersInjectionMethods membersInjectionMethods;
56   private final ComponentRequirementExpressions componentRequirementExpressions;
57   private final DaggerElements elements;
58   private final SourceVersion sourceVersion;
59   private final KotlinMetadataUtil metadataUtil;
60 
SimpleMethodBindingExpression( ProvisionBinding binding, CompilerOptions compilerOptions, ComponentBindingExpressions componentBindingExpressions, MembersInjectionMethods membersInjectionMethods, ComponentRequirementExpressions componentRequirementExpressions, DaggerElements elements, SourceVersion sourceVersion, KotlinMetadataUtil metadataUtil)61   SimpleMethodBindingExpression(
62       ProvisionBinding binding,
63       CompilerOptions compilerOptions,
64       ComponentBindingExpressions componentBindingExpressions,
65       MembersInjectionMethods membersInjectionMethods,
66       ComponentRequirementExpressions componentRequirementExpressions,
67       DaggerElements elements,
68       SourceVersion sourceVersion,
69       KotlinMetadataUtil metadataUtil) {
70     super(binding);
71     this.compilerOptions = compilerOptions;
72     this.provisionBinding = binding;
73     this.metadataUtil = metadataUtil;
74     checkArgument(
75         provisionBinding.implicitDependencies().isEmpty(),
76         "framework deps are not currently supported");
77     checkArgument(provisionBinding.bindingElement().isPresent());
78     this.componentBindingExpressions = componentBindingExpressions;
79     this.membersInjectionMethods = membersInjectionMethods;
80     this.componentRequirementExpressions = componentRequirementExpressions;
81     this.elements = elements;
82     this.sourceVersion = sourceVersion;
83   }
84 
85   @Override
getDependencyExpression(ClassName requestingClass)86   Expression getDependencyExpression(ClassName requestingClass) {
87     return requiresInjectionMethod(provisionBinding, compilerOptions, requestingClass)
88         ? invokeInjectionMethod(requestingClass)
89         : invokeMethod(requestingClass);
90   }
91 
invokeMethod(ClassName requestingClass)92   private Expression invokeMethod(ClassName requestingClass) {
93     // TODO(dpb): align this with the contents of InlineMethods.create
94     CodeBlock arguments =
95         makeParametersCodeBlock(
96             ProvisionMethod.invokeArguments(
97                 provisionBinding,
98                 request -> dependencyArgument(request, requestingClass).codeBlock(),
99                 requestingClass));
100     ExecutableElement method = asExecutable(provisionBinding.bindingElement().get());
101     CodeBlock invocation;
102     switch (method.getKind()) {
103       case CONSTRUCTOR:
104         invocation = CodeBlock.of("new $T($L)", constructorTypeName(requestingClass), arguments);
105         break;
106       case METHOD:
107         CodeBlock module;
108         Optional<CodeBlock> requiredModuleInstance = moduleReference(requestingClass);
109         if (requiredModuleInstance.isPresent()) {
110           module = requiredModuleInstance.get();
111         } else if (metadataUtil.isObjectClass(asType(method.getEnclosingElement()))) {
112           // Call through the singleton instance.
113           // See: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#static-methods
114           module = CodeBlock.of("$T.INSTANCE", provisionBinding.bindingTypeElement().get());
115         } else {
116           module = CodeBlock.of("$T", provisionBinding.bindingTypeElement().get());
117         }
118         invocation = CodeBlock.of("$L.$L($L)", module, method.getSimpleName(), arguments);
119         break;
120       default:
121         throw new IllegalStateException();
122     }
123 
124     return Expression.create(simpleMethodReturnType(), invocation);
125   }
126 
constructorTypeName(ClassName requestingClass)127   private TypeName constructorTypeName(ClassName requestingClass) {
128     DeclaredType type = MoreTypes.asDeclared(provisionBinding.key().type());
129     TypeName typeName = TypeName.get(type);
130     if (type.getTypeArguments().stream()
131         .allMatch(t -> isTypeAccessibleFrom(t, requestingClass.packageName()))) {
132       return typeName;
133     }
134     return rawTypeName(typeName);
135   }
136 
invokeInjectionMethod(ClassName requestingClass)137   private Expression invokeInjectionMethod(ClassName requestingClass) {
138     return injectMembers(
139         ProvisionMethod.invoke(
140             provisionBinding,
141             request -> dependencyArgument(request, requestingClass).codeBlock(),
142             requestingClass,
143             moduleReference(requestingClass),
144             compilerOptions,
145             metadataUtil));
146   }
147 
dependencyArgument(DependencyRequest dependency, ClassName requestingClass)148   private Expression dependencyArgument(DependencyRequest dependency, ClassName requestingClass) {
149     return componentBindingExpressions.getDependencyArgumentExpression(dependency, requestingClass);
150   }
151 
injectMembers(CodeBlock instance)152   private Expression injectMembers(CodeBlock instance) {
153     if (provisionBinding.injectionSites().isEmpty()) {
154       return Expression.create(simpleMethodReturnType(), instance);
155     }
156     if (sourceVersion.compareTo(SourceVersion.RELEASE_7) <= 0) {
157       // Java 7 type inference can't figure out that instance in
158       // injectParameterized(Parameterized_Factory.newParameterized()) is Parameterized<T> and not
159       // Parameterized<Object>
160       if (!MoreTypes.asDeclared(provisionBinding.key().type()).getTypeArguments().isEmpty()) {
161         TypeName keyType = TypeName.get(provisionBinding.key().type());
162         instance = CodeBlock.of("($T) ($T) $L", keyType, rawTypeName(keyType), instance);
163       }
164     }
165     MethodSpec membersInjectionMethod = membersInjectionMethods.getOrCreate(provisionBinding.key());
166     TypeMirror returnType =
167         membersInjectionMethod.returnType.equals(TypeName.OBJECT)
168             ? elements.getTypeElement(Object.class).asType()
169             : provisionBinding.key().type();
170     return Expression.create(returnType, CodeBlock.of("$N($L)", membersInjectionMethod, instance));
171   }
172 
moduleReference(ClassName requestingClass)173   private Optional<CodeBlock> moduleReference(ClassName requestingClass) {
174     return provisionBinding.requiresModuleInstance()
175         ? provisionBinding
176             .contributingModule()
177             .map(Element::asType)
178             .map(ComponentRequirement::forModule)
179             .map(module -> componentRequirementExpressions.getExpression(module, requestingClass))
180         : Optional.empty();
181   }
182 
simpleMethodReturnType()183   private TypeMirror simpleMethodReturnType() {
184     return provisionBinding.contributedPrimitiveType().orElse(provisionBinding.key().type());
185   }
186 }
187