| /* |
| * 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; |
| } |
| } |
| |
| } |