1 /*
2  * Copyright (C) 2017 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 com.squareup.javapoet.MethodSpec.methodBuilder;
21 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
22 import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom;
23 import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.MEMBERS_INJECTION_METHOD;
24 import static javax.lang.model.element.Modifier.PRIVATE;
25 
26 import com.google.common.collect.ImmutableSet;
27 import com.squareup.javapoet.ClassName;
28 import com.squareup.javapoet.CodeBlock;
29 import com.squareup.javapoet.MethodSpec;
30 import com.squareup.javapoet.ParameterSpec;
31 import com.squareup.javapoet.TypeName;
32 import dagger.internal.codegen.binding.Binding;
33 import dagger.internal.codegen.binding.BindingGraph;
34 import dagger.internal.codegen.binding.MembersInjectionBinding;
35 import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite;
36 import dagger.internal.codegen.binding.ProvisionBinding;
37 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
38 import dagger.internal.codegen.langmodel.DaggerElements;
39 import dagger.internal.codegen.langmodel.DaggerTypes;
40 import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod;
41 import dagger.model.Key;
42 import java.util.LinkedHashMap;
43 import java.util.Map;
44 import javax.lang.model.element.Name;
45 import javax.lang.model.element.TypeElement;
46 import javax.lang.model.type.TypeMirror;
47 
48 /** Manages the member injection methods for a component. */
49 final class MembersInjectionMethods {
50   private final Map<Key, MethodSpec> membersInjectionMethods = new LinkedHashMap<>();
51   private final ComponentImplementation componentImplementation;
52   private final ComponentBindingExpressions bindingExpressions;
53   private final BindingGraph graph;
54   private final DaggerElements elements;
55   private final DaggerTypes types;
56   private final KotlinMetadataUtil metadataUtil;
57 
MembersInjectionMethods( ComponentImplementation componentImplementation, ComponentBindingExpressions bindingExpressions, BindingGraph graph, DaggerElements elements, DaggerTypes types, KotlinMetadataUtil metadataUtil)58   MembersInjectionMethods(
59       ComponentImplementation componentImplementation,
60       ComponentBindingExpressions bindingExpressions,
61       BindingGraph graph,
62       DaggerElements elements,
63       DaggerTypes types,
64       KotlinMetadataUtil metadataUtil) {
65     this.componentImplementation = checkNotNull(componentImplementation);
66     this.bindingExpressions = checkNotNull(bindingExpressions);
67     this.graph = checkNotNull(graph);
68     this.elements = checkNotNull(elements);
69     this.types = checkNotNull(types);
70     this.metadataUtil = metadataUtil;
71   }
72 
73   /**
74    * Returns the members injection {@link MethodSpec} for the given {@link Key}, creating it if
75    * necessary.
76    */
getOrCreate(Key key)77   MethodSpec getOrCreate(Key key) {
78     return reentrantComputeIfAbsent(membersInjectionMethods, key, this::membersInjectionMethod);
79   }
80 
membersInjectionMethod(Key key)81   private MethodSpec membersInjectionMethod(Key key) {
82     Binding binding =
83         graph.membersInjectionBinding(key).isPresent()
84             ? graph.membersInjectionBinding(key).get()
85             : graph.contributionBinding(key);
86     TypeMirror keyType = binding.key().type();
87     TypeMirror membersInjectedType =
88         isTypeAccessibleFrom(keyType, componentImplementation.name().packageName())
89             ? keyType
90             : elements.getTypeElement(Object.class).asType();
91     TypeName membersInjectedTypeName = TypeName.get(membersInjectedType);
92     Name bindingTypeName = binding.bindingTypeElement().get().getSimpleName();
93     // TODO(ronshapiro): include type parameters in this name e.g. injectFooOfT, and outer class
94     // simple names Foo.Builder -> injectFooBuilder
95     String methodName = componentImplementation.getUniqueMethodName("inject" + bindingTypeName);
96     ParameterSpec parameter = ParameterSpec.builder(membersInjectedTypeName, "instance").build();
97     MethodSpec.Builder methodBuilder =
98         methodBuilder(methodName)
99             .addModifiers(PRIVATE)
100             .returns(membersInjectedTypeName)
101             .addParameter(parameter);
102     TypeElement canIgnoreReturnValue =
103         elements.getTypeElement("com.google.errorprone.annotations.CanIgnoreReturnValue");
104     if (canIgnoreReturnValue != null) {
105       methodBuilder.addAnnotation(ClassName.get(canIgnoreReturnValue));
106     }
107     CodeBlock instance = CodeBlock.of("$N", parameter);
108     methodBuilder.addCode(
109         InjectionSiteMethod.invokeAll(
110             injectionSites(binding),
111             componentImplementation.name(),
112             instance,
113             membersInjectedType,
114             request ->
115                 bindingExpressions
116                     .getDependencyArgumentExpression(request, componentImplementation.name())
117                     .codeBlock(),
118             types,
119             metadataUtil));
120     methodBuilder.addStatement("return $L", instance);
121 
122     MethodSpec method = methodBuilder.build();
123     componentImplementation.addMethod(MEMBERS_INJECTION_METHOD, method);
124     return method;
125   }
126 
injectionSites(Binding binding)127   private static ImmutableSet<InjectionSite> injectionSites(Binding binding) {
128     if (binding instanceof ProvisionBinding) {
129       return ((ProvisionBinding) binding).injectionSites();
130     } else if (binding instanceof MembersInjectionBinding) {
131       return ((MembersInjectionBinding) binding).injectionSites();
132     }
133     throw new IllegalArgumentException(binding.key().toString());
134   }
135 }
136