Files
Android11/external/dagger2/java/dagger/internal/codegen/FrameworkFieldInitializer.java
2023-10-13 14:01:41 +00:00

218 lines
8.5 KiB
Java

/*
* Copyright (C) 2015 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.Preconditions.checkNotNull;
import static dagger.internal.codegen.ComponentImplementation.FieldSpecKind.FRAMEWORK_FIELD;
import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES;
import static javax.lang.model.element.Modifier.PRIVATE;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.TypeName;
import dagger.internal.DelegateFactory;
import dagger.internal.codegen.javapoet.AnnotationSpecs;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.producers.internal.DelegateProducer;
import java.util.Optional;
/**
* An object that can initialize a framework-type component field for a binding. An instance should
* be created for each field.
*/
class FrameworkFieldInitializer implements FrameworkInstanceSupplier {
/**
* An object that can determine the expression to use to assign to the component field for a
* binding.
*/
interface FrameworkInstanceCreationExpression {
/** Returns the expression to use to assign to the component field for the binding. */
CodeBlock creationExpression();
/**
* Returns the framework class to use for the field, if different from the one implied by the
* binding. This implementation returns {@link Optional#empty()}.
*/
default Optional<ClassName> alternativeFrameworkClass() {
return Optional.empty();
}
/**
* Returns {@code true} if instead of using {@link #creationExpression()} to create a framework
* instance, a case in {@link InnerSwitchingProviders} should be created for this binding.
*/
// TODO(ronshapiro): perhaps this isn't the right approach. Instead of saying "Use
// SetFactory.EMPTY because you will only get 1 class for all types of bindings that use
// SetFactory", maybe we should still use an inner switching provider but the same switching
// provider index for all cases.
default boolean useInnerSwitchingProvider() {
return true;
}
}
private final ComponentImplementation componentImplementation;
private final ResolvedBindings resolvedBindings;
private final FrameworkInstanceCreationExpression frameworkInstanceCreationExpression;
private FieldSpec fieldSpec;
private InitializationState fieldInitializationState = InitializationState.UNINITIALIZED;
FrameworkFieldInitializer(
ComponentImplementation componentImplementation,
ResolvedBindings resolvedBindings,
FrameworkInstanceCreationExpression frameworkInstanceCreationExpression) {
this.componentImplementation = checkNotNull(componentImplementation);
this.resolvedBindings = checkNotNull(resolvedBindings);
this.frameworkInstanceCreationExpression = checkNotNull(frameworkInstanceCreationExpression);
}
/**
* Returns the {@link MemberSelect} for the framework field, and adds the field and its
* initialization code to the component if it's needed and not already added.
*/
@Override
public final MemberSelect memberSelect() {
initializeField();
return MemberSelect.localField(componentImplementation.name(), checkNotNull(fieldSpec).name);
}
/** Adds the field and its initialization code to the component. */
private void initializeField() {
switch (fieldInitializationState) {
case UNINITIALIZED:
// Change our state in case we are recursively invoked via initializeBindingExpression
fieldInitializationState = InitializationState.INITIALIZING;
CodeBlock.Builder codeBuilder = CodeBlock.builder();
CodeBlock fieldInitialization = frameworkInstanceCreationExpression.creationExpression();
CodeBlock initCode = CodeBlock.of("this.$N = $L;", getOrCreateField(), fieldInitialization);
if (isReplacingSuperclassFrameworkInstance()
|| fieldInitializationState == InitializationState.DELEGATED) {
codeBuilder.add(
"$T.setDelegate($N, $L);", delegateType(), fieldSpec, fieldInitialization);
} else {
codeBuilder.add(initCode);
}
componentImplementation.addInitialization(codeBuilder.build());
fieldInitializationState = InitializationState.INITIALIZED;
break;
case INITIALIZING:
// We were recursively invoked, so create a delegate factory instead
fieldInitializationState = InitializationState.DELEGATED;
componentImplementation.addInitialization(
CodeBlock.of("this.$N = new $T<>();", getOrCreateField(), delegateType()));
break;
case DELEGATED:
case INITIALIZED:
break;
}
}
/**
* Adds a field representing the resolved bindings, optionally forcing it to use a particular
* binding type (instead of the type the resolved bindings would typically use).
*/
private FieldSpec getOrCreateField() {
if (fieldSpec != null) {
return fieldSpec;
}
boolean useRawType = !componentImplementation.isTypeAccessible(resolvedBindings.key().type());
FrameworkField contributionBindingField =
FrameworkField.forResolvedBindings(
resolvedBindings, frameworkInstanceCreationExpression.alternativeFrameworkClass());
TypeName fieldType =
useRawType ? contributionBindingField.type().rawType : contributionBindingField.type();
FieldSpec.Builder contributionField =
FieldSpec.builder(
fieldType, componentImplementation.getUniqueFieldName(contributionBindingField.name()));
contributionField.addModifiers(PRIVATE);
if (useRawType) {
contributionField.addAnnotation(AnnotationSpecs.suppressWarnings(RAWTYPES));
}
if (isReplacingSuperclassFrameworkInstance()) {
// If a binding is modified in a subclass, the framework instance will be replaced in the
// subclass implementation. The superclass framework instance initialization will run first,
// however, and may refer to the modifiable binding method returning this type's modified
// framework instance before it is initialized, so we use a delegate factory as a placeholder
// until it has properly been initialized.
contributionField.initializer("new $T<>()", delegateType());
}
fieldSpec = contributionField.build();
componentImplementation.addField(FRAMEWORK_FIELD, fieldSpec);
return fieldSpec;
}
/**
* Returns true if this framework field is replacing a superclass's implementation of the
* framework field.
*/
private boolean isReplacingSuperclassFrameworkInstance() {
return componentImplementation
.superclassImplementation()
.flatMap(
superclassImplementation ->
// TODO(b/117833324): can we constrain this further?
superclassImplementation.getModifiableBindingMethod(
BindingRequest.bindingRequest(
resolvedBindings.key(),
isProvider() ? FrameworkType.PROVIDER : FrameworkType.PRODUCER_NODE)))
.isPresent();
}
private Class<?> delegateType() {
return isProvider() ? DelegateFactory.class : DelegateProducer.class;
}
private boolean isProvider() {
return resolvedBindings.bindingType().equals(BindingType.PROVISION)
&& frameworkInstanceCreationExpression
.alternativeFrameworkClass()
.map(TypeNames.PROVIDER::equals)
.orElse(true);
}
/** Initialization state for a factory field. */
private enum InitializationState {
/** The field is {@code null}. */
UNINITIALIZED,
/**
* The field's dependencies are being set up. If the field is needed in this state, use a {@link
* DelegateFactory}.
*/
INITIALIZING,
/**
* The field's dependencies are being set up, but the field can be used because it has already
* been set to a {@link DelegateFactory}.
*/
DELEGATED,
/** The field is set to an undelegated factory. */
INITIALIZED;
}
}