blob: c84b4ca91a3f674ebaacf5d2df3d2f210dbdffd2 [file] [log] [blame]
/*
* Copyright (C) 2017 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;
import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
import static com.squareup.javapoet.MethodSpec.methodBuilder;
import static dagger.internal.codegen.CodeBlocks.anonymousProvider;
import static dagger.internal.codegen.ContributionBinding.FactoryCreationStrategy.SINGLETON_INSTANCE;
import static dagger.internal.codegen.GeneratedComponentModel.FieldSpecKind.PRIVATE_METHOD_SCOPED_FIELD;
import static dagger.internal.codegen.GeneratedComponentModel.MethodSpecKind.PRIVATE_METHOD;
import static dagger.internal.codegen.RequestKinds.requestType;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.VOLATILE;
import com.google.auto.common.MoreTypes;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import dagger.internal.MemoizedSentinel;
import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
import dagger.model.RequestKind;
import dagger.model.Scope;
import java.util.EnumMap;
import java.util.Map;
import java.util.Optional;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
/**
* A binding expression that wraps the dependency expressions in a private, no-arg method.
*
* <p>Dependents of this binding expression will just called the no-arg method.
*/
final class PrivateMethodBindingExpression extends BindingExpression {
private final GeneratedComponentModel generatedComponentModel;
private final ComponentBindingExpressions componentBindingExpressions;
private final BindingExpression delegate;
// TODO(user): No need for a map. Each PMBE instance only handles 1 request kind now.
private final Map<RequestKind, String> methodNames = new EnumMap<>(RequestKind.class);
private final Map<RequestKind, String> fieldNames = new EnumMap<>(RequestKind.class);
private final ContributionBinding binding;
private final CompilerOptions compilerOptions;
private final ReferenceReleasingManagerFields referenceReleasingManagerFields;
private final DaggerTypes types;
private final Elements elements;
PrivateMethodBindingExpression(
GeneratedComponentModel generatedComponentModel,
ComponentBindingExpressions componentBindingExpressions,
BindingExpression delegate,
ReferenceReleasingManagerFields referenceReleasingManagerFields,
CompilerOptions compilerOptions,
DaggerTypes types,
Elements elements) {
super(delegate.resolvedBindings(), delegate.requestKind());
this.generatedComponentModel = generatedComponentModel;
this.componentBindingExpressions = componentBindingExpressions;
this.delegate = delegate;
this.binding = resolvedBindings().contributionBinding();
this.referenceReleasingManagerFields = referenceReleasingManagerFields;
this.compilerOptions = compilerOptions;
this.types = types;
this.elements = elements;
}
@Override
protected CodeBlock doGetComponentMethodImplementation(
ComponentMethodDescriptor componentMethod, ClassName requestingClass) {
if (!canInlineScope() && ignorePrivateMethodStrategy()) {
return delegate.getComponentMethodImplementation(componentMethod, requestingClass);
}
return findComponentMethod().map(method -> method.equals(componentMethod)).orElse(false)
? methodBody()
: super.doGetComponentMethodImplementation(componentMethod, requestingClass);
}
@Override
Expression getDependencyExpression(ClassName requestingClass) {
if (!canInlineScope() && (ignorePrivateMethodStrategy() || isNullaryProvisionMethod())) {
return delegate.getDependencyExpression(requestingClass);
}
if (!methodNames.containsKey(requestKind())) {
Optional<ComponentMethodDescriptor> componentMethod = findComponentMethod();
String name =
componentMethod.isPresent()
? componentMethod.get().methodElement().getSimpleName().toString()
: generatedComponentModel.getUniqueMethodName(methodName());
methodNames.put(requestKind(), name);
if (!componentMethod.isPresent()) {
createMethod(name);
}
}
CodeBlock invocation =
componentName().equals(requestingClass)
? CodeBlock.of("$N()", methodNames.get(requestKind()))
: CodeBlock.of("$T.this.$N()", componentName(), methodNames.get(requestKind()));
return Expression.create(returnType(), invocation);
}
private ClassName componentName() {
return generatedComponentModel.name();
}
// TODO(user): Invert this method to return true if we are using the private method strategy.
private boolean ignorePrivateMethodStrategy() {
// TODO(user): move experimental android logic out of this class
switch (requestKind()) {
case INSTANCE:
case FUTURE:
return false;
case PROVIDER:
case LAZY:
case PROVIDER_OF_LAZY:
return !compilerOptions.experimentalAndroidMode()
|| (binding.scope().isPresent() && !canInlineScope())
|| binding.factoryCreationStrategy().equals(SINGLETON_INSTANCE);
default:
return !compilerOptions.experimentalAndroidMode();
}
}
private boolean isNullaryProvisionMethod() {
return (requestKind().equals(RequestKind.INSTANCE) || requestKind().equals(RequestKind.FUTURE))
&& binding.dependencies().isEmpty()
&& !findComponentMethod().isPresent();
}
private boolean canInlineScope() {
// TODO(user): Enable for releasable references
return compilerOptions.experimentalAndroidMode()
&& binding.scope().isPresent()
&& !referenceReleasingManagerFields.requiresReleasableReferences(binding.scope().get());
}
/** Returns the first component method associated with this request kind, if one exists. */
private Optional<ComponentMethodDescriptor> findComponentMethod() {
// There could be multiple component methods with the same request key and kind.
// We arbitrarily choose the first one, and designate it to contain the implementation code.
return resolvedBindings()
.owningComponent()
.componentMethods()
.stream()
.filter(this::componentMethodMatchesRequestKeyAndKind)
.findFirst();
}
/** Returns true if the component method matches the dependency request key and kind. */
private boolean componentMethodMatchesRequestKeyAndKind(
ComponentMethodDescriptor componentMethod) {
return componentMethod
.dependencyRequest()
.filter(request -> request.key().equals(key()))
.filter(request -> request.kind().equals(requestKind()))
.isPresent();
}
/** Creates the no-arg method used for dependency expressions. */
private void createMethod(String name) {
// TODO(user): Consider when we can make this method static.
// TODO(user): Fix the order that these generated methods are written to the component.
generatedComponentModel.addMethod(
PRIVATE_METHOD,
methodBuilder(name)
.addModifiers(PRIVATE)
.returns(TypeName.get(returnType()))
.addCode(methodBody())
.build());
}
/** Returns the return type for the dependency request. */
private TypeMirror returnType() {
if (requestKind().equals(RequestKind.INSTANCE)
&& binding.contributedPrimitiveType().isPresent()) {
return binding.contributedPrimitiveType().get();
}
return accessibleType(requestType(requestKind(), binding.contributedType(), types));
}
/** Returns the method body for the dependency request. */
private CodeBlock methodBody() {
switch (requestKind()) {
case PROVIDER:
// TODO(user): Cache provider field instead of recreating each time.
return CodeBlock.of("return $L;", providerTypeSpec());
case INSTANCE:
if (canInlineScope()) {
Scope scope = resolvedBindings().scope().get();
return scope.isReusable() ? singleCheck() : doubleCheck();
}
// fall through
default:
return CodeBlock.of(
"return $L;", delegate.getDependencyExpression(componentName()).codeBlock());
}
}
private CodeBlock singleCheck() {
String fieldName = getMemoizedFieldName();
return CodeBlock.builder()
.beginControlFlow("if ($N instanceof $T)", fieldName, MemoizedSentinel.class)
.addStatement(
"$N = $L", fieldName, delegate.getDependencyExpression(componentName()).codeBlock())
.endControlFlow()
.addStatement("return ($T) $N", returnType(), fieldName)
.build();
}
private CodeBlock doubleCheck() {
String fieldName = getMemoizedFieldName();
// add "this." if the fieldName clashes with the local variable name.
fieldName = fieldName.contentEquals("local") ? "this." + fieldName : fieldName;
return CodeBlock.builder()
.addStatement("$T local = $L", TypeName.OBJECT, fieldName)
.beginControlFlow("if (local instanceof $T)", MemoizedSentinel.class)
.beginControlFlow("synchronized (local)")
// TODO(user): benchmark to see if this is really faster than instanceof check?
.beginControlFlow("if (local == $L)", fieldName)
.addStatement(
"$L = $L", fieldName, delegate.getDependencyExpression(componentName()).codeBlock())
.endControlFlow()
.addStatement("local = $L", fieldName)
.endControlFlow()
.endControlFlow()
.addStatement("return ($T) local", returnType())
.build();
}
private String getMemoizedFieldName() {
if (!fieldNames.containsKey(requestKind())) {
String name = generatedComponentModel.getUniqueFieldName(BindingVariableNamer.name(binding));
generatedComponentModel.addField(
PRIVATE_METHOD_SCOPED_FIELD,
FieldSpec.builder(TypeName.OBJECT, name, PRIVATE, VOLATILE)
.initializer("new $T()", MemoizedSentinel.class)
.build());
fieldNames.put(requestKind(), name);
}
return fieldNames.get(requestKind());
}
/** Returns a {@link TypeSpec} for an anonymous provider class. */
private CodeBlock providerTypeSpec() {
// TODO(user): For scoped bindings that have already been created, use InstanceFactory?
return anonymousProvider(
TypeName.get(accessibleType(binding.contributedType())),
CodeBlock.of(
"return $L;",
componentBindingExpressions
.getDependencyExpression(key(), RequestKind.INSTANCE, componentName())
.codeBlock()));
}
/** Returns the canonical name for a no-arg dependency expression method. */
private String methodName() {
// TODO(user): Use a better name for @MapKey binding instances.
// TODO(user): Include the binding method as part of the method name.
if (requestKind().equals(RequestKind.INSTANCE)) {
return "get" + bindingName();
}
return "get" + bindingName() + dependencyKindName(requestKind());
}
/** Returns the canonical name for the {@link Binding}. */
private String bindingName() {
return LOWER_CAMEL.to(UPPER_CAMEL, BindingVariableNamer.name(binding));
}
/** Returns a canonical name for the {@link RequestKind}. */
private static String dependencyKindName(RequestKind kind) {
return UPPER_UNDERSCORE.to(UPPER_CAMEL, kind.name());
}
/** Returns a {@link TypeName} for the binding that is accessible to the component. */
private TypeMirror accessibleType(TypeMirror typeMirror) {
if (Accessibility.isTypeAccessibleFrom(typeMirror, componentName().packageName())) {
return typeMirror;
} else if (Accessibility.isRawTypeAccessible(typeMirror, componentName().packageName())
&& typeMirror.getKind().equals(TypeKind.DECLARED)) {
return types.getDeclaredType(MoreTypes.asTypeElement(typeMirror));
} else {
return elements.getTypeElement(Object.class.getCanonicalName()).asType();
}
}
}