blob: 9c0c74acae99b1f1770d85cf500edc53e8d197ac [file] [log] [blame]
/*
* Copyright (C) 2018 The Dagger Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dagger.internal.codegen.writing;
import static com.google.common.base.Preconditions.checkNotNull;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedParameterSpecs;
import static dagger.internal.codegen.javapoet.CodeBlocks.parameterNames;
import static dagger.internal.codegen.writing.ComponentImplementation.FieldSpecKind.PRIVATE_METHOD_SCOPED_FIELD;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.VOLATILE;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.TypeName;
import dagger.internal.DoubleCheck;
import dagger.internal.MemoizedSentinel;
import dagger.internal.codegen.binding.BindingRequest;
import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor;
import dagger.internal.codegen.binding.ContributionBinding;
import dagger.internal.codegen.binding.FrameworkField;
import dagger.internal.codegen.binding.KeyVariableNamer;
import dagger.internal.codegen.javapoet.Expression;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.model.BindingKind;
import dagger.model.RequestKind;
import java.util.Optional;
import javax.lang.model.type.TypeMirror;
/** A binding expression that wraps another in a nullary method on the component. */
abstract class MethodBindingExpression extends BindingExpression {
private final BindingRequest request;
private final ContributionBinding binding;
private final BindingMethodImplementation bindingMethodImplementation;
private final ComponentImplementation componentImplementation;
private final ProducerEntryPointView producerEntryPointView;
private final BindingExpression wrappedBindingExpression;
private final DaggerTypes types;
protected MethodBindingExpression(
BindingRequest request,
ContributionBinding binding,
MethodImplementationStrategy methodImplementationStrategy,
BindingExpression wrappedBindingExpression,
ComponentImplementation componentImplementation,
DaggerTypes types) {
this.request = checkNotNull(request);
this.binding = checkNotNull(binding);
this.bindingMethodImplementation = bindingMethodImplementation(methodImplementationStrategy);
this.wrappedBindingExpression = checkNotNull(wrappedBindingExpression);
this.componentImplementation = checkNotNull(componentImplementation);
this.producerEntryPointView = new ProducerEntryPointView(types);
this.types = checkNotNull(types);
}
@Override
Expression getDependencyExpression(ClassName requestingClass) {
if (request.frameworkType().isPresent()) {
// Initializing a framework instance that participates in a cycle requires that the underlying
// FrameworkInstanceBindingExpression is invoked in order for a cycle to be detected properly.
// When a MethodBindingExpression wraps a FrameworkInstanceBindingExpression, the wrapped
// expression will only be invoked once to implement the method body. This is a hack to work
// around that weirdness - methodImplementation.body() will invoke the framework instance
// initialization again in case the field is not fully initialized.
// TODO(b/121196706): use a less hacky approach to fix this bug
Object unused = methodBody();
}
addMethod();
CodeBlock methodCall =
binding.kind() == BindingKind.ASSISTED_INJECTION
// Private methods for assisted injection take assisted parameters as input.
? CodeBlock.of(
"$N($L)", methodName(), parameterNames(assistedParameterSpecs(binding, types)))
: CodeBlock.of("$N()", methodName());
return Expression.create(
returnType(),
requestingClass.equals(componentImplementation.name())
? methodCall
: CodeBlock.of("$L.$L", componentImplementation.externalReferenceBlock(), methodCall));
}
@Override
Expression getDependencyExpressionForComponentMethod(ComponentMethodDescriptor componentMethod,
ComponentImplementation component) {
return producerEntryPointView
.getProducerEntryPointField(this, componentMethod, component)
.orElseGet(
() -> super.getDependencyExpressionForComponentMethod(componentMethod, component));
}
/** Adds the method to the component (if necessary) the first time it's called. */
protected abstract void addMethod();
/** Returns the name of the method to call. */
protected abstract String methodName();
/** The method's body. */
protected final CodeBlock methodBody() {
return implementation(
wrappedBindingExpression.getDependencyExpression(componentImplementation.name())
::codeBlock);
}
/** The method's body if this method is a component method. */
protected final CodeBlock methodBodyForComponentMethod(
ComponentMethodDescriptor componentMethod) {
return implementation(
wrappedBindingExpression.getDependencyExpressionForComponentMethod(
componentMethod, componentImplementation)
::codeBlock);
}
private CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression) {
return bindingMethodImplementation.implementation(simpleBindingExpression);
}
private BindingMethodImplementation bindingMethodImplementation(
MethodImplementationStrategy methodImplementationStrategy) {
switch (methodImplementationStrategy) {
case SIMPLE:
return new SimpleMethodImplementation();
case SINGLE_CHECK:
return new SingleCheckedMethodImplementation();
case DOUBLE_CHECK:
return new DoubleCheckedMethodImplementation();
}
throw new AssertionError(methodImplementationStrategy);
}
/** Returns the return type for the dependency request. */
protected TypeMirror returnType() {
if (request.isRequestKind(RequestKind.INSTANCE)
&& binding.contributedPrimitiveType().isPresent()) {
return binding.contributedPrimitiveType().get();
}
if (matchingComponentMethod().isPresent()) {
// Component methods are part of the user-defined API, and thus we must use the user-defined
// type.
return matchingComponentMethod().get().resolvedReturnType(types);
}
TypeMirror requestedType = request.requestedType(binding.contributedType(), types);
return types.accessibleType(requestedType, componentImplementation.name());
}
private Optional<ComponentMethodDescriptor> matchingComponentMethod() {
return componentImplementation.componentDescriptor().firstMatchingComponentMethod(request);
}
/** Strateg for implementing the body of this method. */
enum MethodImplementationStrategy {
SIMPLE,
SINGLE_CHECK,
DOUBLE_CHECK,
;
}
private abstract static class BindingMethodImplementation {
/**
* Returns the method body, which contains zero or more statements (including semicolons).
*
* <p>If the implementation has a non-void return type, the body will also include the {@code
* return} statement.
*
* @param simpleBindingExpression the expression to retrieve an instance of this binding without
* the wrapping method.
*/
abstract CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression);
}
/** Returns the {@code wrappedBindingExpression} directly. */
private static final class SimpleMethodImplementation extends BindingMethodImplementation {
@Override
CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression) {
return CodeBlock.of("return $L;", simpleBindingExpression.get());
}
}
/**
* Defines a method body for single checked caching of the given {@code wrappedBindingExpression}.
*/
private final class SingleCheckedMethodImplementation extends BindingMethodImplementation {
private final Supplier<FieldSpec> field = Suppliers.memoize(this::createField);
@Override
CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression) {
String fieldExpression = field.get().name.equals("local") ? "this.local" : field.get().name;
CodeBlock.Builder builder = CodeBlock.builder()
.addStatement("Object local = $N", fieldExpression);
if (isNullable()) {
builder.beginControlFlow("if (local instanceof $T)", MemoizedSentinel.class);
} else {
builder.beginControlFlow("if (local == null)");
}
return builder
.addStatement("local = $L", simpleBindingExpression.get())
.addStatement("$N = ($T) local", fieldExpression, returnType())
.endControlFlow()
.addStatement("return ($T) local", returnType())
.build();
}
FieldSpec createField() {
String name =
componentImplementation.getUniqueFieldName(
request.isRequestKind(RequestKind.INSTANCE)
? KeyVariableNamer.name(binding.key())
: FrameworkField.forBinding(binding, Optional.empty()).name());
FieldSpec.Builder builder = FieldSpec.builder(fieldType(), name, PRIVATE, VOLATILE);
if (isNullable()) {
builder.initializer("new $T()", MemoizedSentinel.class);
}
FieldSpec field = builder.build();
componentImplementation.addField(PRIVATE_METHOD_SCOPED_FIELD, field);
return field;
}
TypeName fieldType() {
if (isNullable()) {
// Nullable instances use `MemoizedSentinel` instead of `null` as the initialization value,
// so the field type must accept that and the return type
return TypeName.OBJECT;
}
TypeName returnType = TypeName.get(returnType());
return returnType.isPrimitive() ? returnType.box() : returnType;
}
private boolean isNullable() {
return request.isRequestKind(RequestKind.INSTANCE) && binding.isNullable();
}
}
/**
* Defines a method body for double checked caching of the given {@code wrappedBindingExpression}.
*/
private final class DoubleCheckedMethodImplementation extends BindingMethodImplementation {
private final Supplier<String> fieldName = Suppliers.memoize(this::createField);
@Override
CodeBlock implementation(Supplier<CodeBlock> simpleBindingExpression) {
String fieldExpression = fieldName.get().equals("local") ? "this.local" : fieldName.get();
return CodeBlock.builder()
.addStatement("$T local = $L", TypeName.OBJECT, fieldExpression)
.beginControlFlow("if (local instanceof $T)", MemoizedSentinel.class)
.beginControlFlow("synchronized (local)")
.addStatement("local = $L", fieldExpression)
.beginControlFlow("if (local instanceof $T)", MemoizedSentinel.class)
.addStatement("local = $L", simpleBindingExpression.get())
.addStatement("$1L = $2T.reentrantCheck($1L, local)", fieldExpression, DoubleCheck.class)
.endControlFlow()
.endControlFlow()
.endControlFlow()
.addStatement("return ($T) local", returnType())
.build();
}
private String createField() {
String name =
componentImplementation.getUniqueFieldName(KeyVariableNamer.name(binding.key()));
componentImplementation.addField(
PRIVATE_METHOD_SCOPED_FIELD,
FieldSpec.builder(TypeName.OBJECT, name, PRIVATE, VOLATILE)
.initializer("new $T()", MemoizedSentinel.class)
.build());
return name;
}
}
}