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