/* * 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 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 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). * *

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 simpleBindingExpression); } /** Returns the {@code wrappedBindingExpression} directly. */ private static final class SimpleMethodImplementation extends BindingMethodImplementation { @Override CodeBlock implementation(Supplier 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 field = Suppliers.memoize(this::createField); @Override CodeBlock implementation(Supplier 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 fieldName = Suppliers.memoize(this::createField); @Override CodeBlock implementation(Supplier 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; } } }