• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static com.google.common.base.Preconditions.checkState;
21 import static dagger.internal.codegen.ComponentImplementation.FieldSpecKind.PRIVATE_METHOD_SCOPED_FIELD;
22 import static javax.lang.model.element.Modifier.PRIVATE;
23 import static javax.lang.model.element.Modifier.VOLATILE;
24 
25 import com.google.common.base.Supplier;
26 import com.google.common.base.Suppliers;
27 import com.squareup.javapoet.ClassName;
28 import com.squareup.javapoet.CodeBlock;
29 import com.squareup.javapoet.FieldSpec;
30 import com.squareup.javapoet.TypeName;
31 import dagger.internal.DoubleCheck;
32 import dagger.internal.MemoizedSentinel;
33 import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
34 import dagger.internal.codegen.ModifiableBindingMethods.ModifiableBindingMethod;
35 import dagger.internal.codegen.javapoet.Expression;
36 import dagger.internal.codegen.langmodel.DaggerTypes;
37 import dagger.model.RequestKind;
38 import java.util.Optional;
39 import javax.lang.model.type.TypeMirror;
40 
41 /** A binding expression that wraps another in a nullary method on the component. */
42 abstract class MethodBindingExpression extends BindingExpression {
43   private final BindingRequest request;
44   private final ResolvedBindings resolvedBindings;
45   private final ContributionBinding binding;
46   private final BindingMethodImplementation bindingMethodImplementation;
47   private final ComponentImplementation componentImplementation;
48   private final ProducerEntryPointView producerEntryPointView;
49   private final BindingExpression wrappedBindingExpression;
50   private final DaggerTypes types;
51 
MethodBindingExpression( BindingRequest request, ResolvedBindings resolvedBindings, MethodImplementationStrategy methodImplementationStrategy, BindingExpression wrappedBindingExpression, ComponentImplementation componentImplementation, DaggerTypes types)52   protected MethodBindingExpression(
53       BindingRequest request,
54       ResolvedBindings resolvedBindings,
55       MethodImplementationStrategy methodImplementationStrategy,
56       BindingExpression wrappedBindingExpression,
57       ComponentImplementation componentImplementation,
58       DaggerTypes types) {
59     this.request = checkNotNull(request);
60     this.resolvedBindings = resolvedBindings;
61     this.binding = resolvedBindings.contributionBinding();
62     this.bindingMethodImplementation = bindingMethodImplementation(methodImplementationStrategy);
63     this.wrappedBindingExpression = checkNotNull(wrappedBindingExpression);
64     this.componentImplementation = checkNotNull(componentImplementation);
65     this.producerEntryPointView = new ProducerEntryPointView(types);
66     this.types = checkNotNull(types);
67   }
68 
69   @Override
getDependencyExpression(ClassName requestingClass)70   Expression getDependencyExpression(ClassName requestingClass) {
71     if (request.frameworkType().isPresent()) {
72       // Initializing a framework instance that participates in a cycle requires that the underlying
73       // FrameworkInstanceBindingExpression is invoked in order for a cycle to be detected properly.
74       // When a MethodBindingExpression wraps a FrameworkInstanceBindingExpression, the wrapped
75       // expression will only be invoked once to implement the method body. This is a hack to work
76       // around that weirdness - methodImplementation.body() will invoke the framework instance
77       // initialization again in case the field is not fully initialized.
78       // TODO(b/121196706): use a less hacky approach to fix this bug
79       Object unused = methodBody();
80     }
81 
82     addMethod();
83     return Expression.create(
84         returnType(),
85         requestingClass.equals(componentImplementation.name())
86             ? CodeBlock.of("$N()", methodName())
87             : CodeBlock.of("$T.this.$N()", componentImplementation.name(), methodName()));
88   }
89 
90   @Override
getModifiableBindingMethodImplementation( ModifiableBindingMethod modifiableBindingMethod, ComponentImplementation component, DaggerTypes types)91   final CodeBlock getModifiableBindingMethodImplementation(
92       ModifiableBindingMethod modifiableBindingMethod,
93       ComponentImplementation component,
94       DaggerTypes types) {
95     // A matching modifiable binding method means that we have previously created the binding method
96     // and we are now implementing it. If there is no matching method we need to first create the
97     // method. We create the method by deferring to getDependencyExpression (defined above) via a
98     // call to super.getModifiableBindingMethodImplementation().
99     if (supertypeModifiableBindingMethod().isPresent()) {
100       checkState(
101           supertypeModifiableBindingMethod().get().fulfillsSameRequestAs(modifiableBindingMethod));
102       return methodBody();
103     }
104     return super.getModifiableBindingMethodImplementation(
105         modifiableBindingMethod, component, types);
106   }
107 
supertypeModifiableBindingMethod()108   protected final Optional<ModifiableBindingMethod> supertypeModifiableBindingMethod() {
109     return componentImplementation.supertypeModifiableBindingMethod(request);
110   }
111 
112   @Override
getDependencyExpressionForComponentMethod(ComponentMethodDescriptor componentMethod, ComponentImplementation component)113   Expression getDependencyExpressionForComponentMethod(ComponentMethodDescriptor componentMethod,
114       ComponentImplementation component) {
115     return producerEntryPointView
116         .getProducerEntryPointField(this, componentMethod, component)
117         .orElseGet(
118             () -> super.getDependencyExpressionForComponentMethod(componentMethod, component));
119   }
120 
121   /** Adds the method to the component (if necessary) the first time it's called. */
addMethod()122   protected abstract void addMethod();
123 
124   /** Returns the name of the method to call. */
methodName()125   protected abstract String methodName();
126 
127   /**
128    * Returns {@code true} if the method of this binding expression is modifiable and is not a
129    * component method.
130    */
isModifiableImplementationMethod()131   protected boolean isModifiableImplementationMethod() {
132     return false;
133   }
134 
135   /** The method's body. */
methodBody()136   protected final CodeBlock methodBody() {
137     return implementation(
138         wrappedBindingExpression.getDependencyExpression(componentImplementation.name())
139             ::codeBlock);
140   }
141 
142   /** The method's body if this method is a component method. */
methodBodyForComponentMethod( ComponentMethodDescriptor componentMethod)143   protected final CodeBlock methodBodyForComponentMethod(
144       ComponentMethodDescriptor componentMethod) {
145     return implementation(
146         wrappedBindingExpression.getDependencyExpressionForComponentMethod(
147                 componentMethod, componentImplementation)
148             ::codeBlock);
149   }
150 
implementation(Supplier<CodeBlock> simpleBindingExpression)151   private CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression) {
152     return bindingMethodImplementation.implementation(simpleBindingExpression);
153   }
154 
bindingMethodImplementation( MethodImplementationStrategy methodImplementationStrategy)155   private BindingMethodImplementation bindingMethodImplementation(
156       MethodImplementationStrategy methodImplementationStrategy) {
157     switch (methodImplementationStrategy) {
158       case SIMPLE:
159         return new SimpleMethodImplementation();
160       case SINGLE_CHECK:
161         return new SingleCheckedMethodImplementation();
162       case DOUBLE_CHECK:
163         return new DoubleCheckedMethodImplementation();
164     }
165     throw new AssertionError(methodImplementationStrategy);
166   }
167 
168   /** Returns the return type for the dependency request. */
returnType()169   protected TypeMirror returnType() {
170     if (request.isRequestKind(RequestKind.INSTANCE)
171         && binding.contributedPrimitiveType().isPresent()) {
172       return binding.contributedPrimitiveType().get();
173     }
174 
175     if (matchingComponentMethod().isPresent()) {
176       // Component methods are part of the user-defined API, and thus we must use the user-defined
177       // type.
178       return matchingComponentMethod().get().resolvedReturnType(types);
179     }
180 
181     // If the component is abstract, this method may be overridden by another implementation in a
182     // different package for which requestedType is inaccessible. In order to make that method
183     // overridable, we use the publicly accessible type. If the method is private, we don't need to
184     // worry about this, and instead just need to check accessibility of the file we're about to
185     // write
186     TypeMirror requestedType = request.requestedType(binding.contributedType(), types);
187     return isModifiableImplementationMethod()
188         ? types.publiclyAccessibleType(requestedType)
189         : types.accessibleType(requestedType, componentImplementation.name());
190   }
191 
matchingComponentMethod()192   private Optional<ComponentMethodDescriptor> matchingComponentMethod() {
193     return componentImplementation.componentDescriptor().firstMatchingComponentMethod(request);
194   }
195 
196   /** Strateg for implementing the body of this method. */
197   enum MethodImplementationStrategy {
198     SIMPLE,
199     SINGLE_CHECK,
200     DOUBLE_CHECK,
201     ;
202   }
203 
204   private abstract static class BindingMethodImplementation {
205     /**
206      * Returns the method body, which contains zero or more statements (including semicolons).
207      *
208      * <p>If the implementation has a non-void return type, the body will also include the {@code
209      * return} statement.
210      *
211      * @param simpleBindingExpression the expression to retrieve an instance of this binding without
212      *     the wrapping method.
213      */
implementation(Supplier<CodeBlock> simpleBindingExpression)214     abstract CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression);
215   }
216 
217   /** Returns the {@code wrappedBindingExpression} directly. */
218   private static final class SimpleMethodImplementation extends BindingMethodImplementation {
219     @Override
implementation(Supplier<CodeBlock> simpleBindingExpression)220     CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression) {
221       return CodeBlock.of("return $L;", simpleBindingExpression.get());
222     }
223   }
224 
225   /**
226    * Defines a method body for single checked caching of the given {@code wrappedBindingExpression}.
227    */
228   private final class SingleCheckedMethodImplementation extends BindingMethodImplementation {
229     private final Supplier<FieldSpec> field = Suppliers.memoize(this::createField);
230 
231     @Override
implementation(Supplier<CodeBlock> simpleBindingExpression)232     CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression) {
233       String fieldExpression = field.get().name.equals("local") ? "this.local" : field.get().name;
234 
235       CodeBlock.Builder builder = CodeBlock.builder()
236           .addStatement("Object local = $N", fieldExpression);
237 
238       if (isNullable()) {
239         builder.beginControlFlow("if (local instanceof $T)", MemoizedSentinel.class);
240       } else {
241         builder.beginControlFlow("if (local == null)");
242       }
243 
244       return builder
245           .addStatement("local = $L", simpleBindingExpression.get())
246           .addStatement("$N = ($T) local", fieldExpression, returnType())
247           .endControlFlow()
248           .addStatement("return ($T) local", returnType())
249           .build();
250     }
251 
createField()252     FieldSpec createField() {
253       String name =
254           componentImplementation.getUniqueFieldName(
255               request.isRequestKind(RequestKind.INSTANCE)
256                   ? KeyVariableNamer.name(binding.key())
257                   // TODO(ronshapiro): Use KeyVariableNamer directly so we don't need to use a
258                   // ResolvedBindings instance and construct a whole framework field just to get the
259                   // name
260                   : FrameworkField.forResolvedBindings(resolvedBindings, Optional.empty()).name());
261 
262       FieldSpec.Builder builder = FieldSpec.builder(fieldType(), name, PRIVATE, VOLATILE);
263       if (isNullable()) {
264         builder.initializer("new $T()", MemoizedSentinel.class);
265       }
266 
267       FieldSpec field = builder.build();
268       componentImplementation.addField(PRIVATE_METHOD_SCOPED_FIELD, field);
269       return field;
270     }
271 
fieldType()272     TypeName fieldType() {
273       if (isNullable()) {
274         // Nullable instances use `MemoizedSentinel` instead of `null` as the initialization value,
275         // so the field type must accept that and the return type
276         return TypeName.OBJECT;
277       }
278       TypeName returnType = TypeName.get(returnType());
279       return returnType.isPrimitive() ? returnType.box() : returnType;
280     }
281 
isNullable()282     private boolean isNullable() {
283       return request.isRequestKind(RequestKind.INSTANCE) && binding.isNullable();
284     }
285   }
286 
287   /**
288    * Defines a method body for double checked caching of the given {@code wrappedBindingExpression}.
289    */
290   private final class DoubleCheckedMethodImplementation extends BindingMethodImplementation {
291     private final Supplier<String> fieldName = Suppliers.memoize(this::createField);
292 
293     @Override
implementation(Supplier<CodeBlock> simpleBindingExpression)294     CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression) {
295       String fieldExpression = fieldName.get().equals("local") ? "this.local" : fieldName.get();
296       return CodeBlock.builder()
297           .addStatement("$T local = $L", TypeName.OBJECT, fieldExpression)
298           .beginControlFlow("if (local instanceof $T)", MemoizedSentinel.class)
299           .beginControlFlow("synchronized (local)")
300           .addStatement("local = $L", fieldExpression)
301           .beginControlFlow("if (local instanceof $T)", MemoizedSentinel.class)
302           .addStatement("local = $L", simpleBindingExpression.get())
303           .addStatement("$1L = $2T.reentrantCheck($1L, local)", fieldExpression, DoubleCheck.class)
304           .endControlFlow()
305           .endControlFlow()
306           .endControlFlow()
307           .addStatement("return ($T) local", returnType())
308           .build();
309     }
310 
createField()311     private String createField() {
312       String name =
313           componentImplementation.getUniqueFieldName(KeyVariableNamer.name(binding.key()));
314       componentImplementation.addField(
315           PRIVATE_METHOD_SCOPED_FIELD,
316           FieldSpec.builder(TypeName.OBJECT, name, PRIVATE, VOLATILE)
317               .initializer("new $T()", MemoizedSentinel.class)
318               .build());
319       return name;
320     }
321   }
322 
323 }
324