unplugged-system/external/dagger2/java/dagger/internal/codegen/binding/ComponentRequirement.java

279 lines
9.7 KiB
Java

/*
* Copyright (C) 2016 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.binding;
import static androidx.room.compiler.processing.XElementKt.isConstructor;
import static androidx.room.compiler.processing.compat.XConverters.toJavac;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static dagger.internal.codegen.binding.SourceFiles.simpleVariableName;
import static dagger.internal.codegen.xprocessing.XElements.asConstructor;
import static dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation;
import static dagger.internal.codegen.xprocessing.XTypeElements.isNested;
import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
import static kotlin.streams.jdk8.StreamsKt.asStream;
import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XMethodElement;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
import com.google.auto.value.AutoValue;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.spi.model.BindingKind;
import dagger.spi.model.Key;
import java.util.Optional;
/** A type that a component needs an instance of. */
@AutoValue
public abstract class ComponentRequirement {
/** The kind of the {@link ComponentRequirement}. */
public enum Kind {
/** A type listed in the component's {@code dependencies} attribute. */
DEPENDENCY,
/** A type listed in the component or subcomponent's {@code modules} attribute. */
MODULE,
/**
* An object that is passed to a builder's {@link dagger.BindsInstance @BindsInstance} method.
*/
BOUND_INSTANCE,
;
public boolean isBoundInstance() {
return equals(BOUND_INSTANCE);
}
public boolean isModule() {
return equals(MODULE);
}
}
private XType type;
/** The kind of requirement. */
public abstract Kind kind();
/** Returns true if this is a {@link Kind#BOUND_INSTANCE} requirement. */
// TODO(ronshapiro): consider removing this and inlining the usages
final boolean isBoundInstance() {
return kind().isBoundInstance();
}
/** The type of the instance the component must have. */
abstract TypeName typeName();
/** The type of the instance the component must have. */
public XType type() {
return type;
}
/** The element associated with the type of this requirement. */
public XTypeElement typeElement() {
return type.getTypeElement();
}
/** The action a component builder should take if it {@code null} is passed. */
public enum NullPolicy {
/** Make a new instance. */
NEW,
/** Throw an exception. */
THROW,
/** Allow use of null values. */
ALLOW,
}
/**
* An override for the requirement's null policy. If set, this is used as the null policy instead
* of the default behavior in {@link #nullPolicy}.
*
* <p>Some implementations' null policy can be determined upon construction (e.g., for binding
* instances), but others' require Elements which must wait until {@link #nullPolicy} is called.
*/
abstract Optional<NullPolicy> overrideNullPolicy();
/** The requirement's null policy. */
public NullPolicy nullPolicy() {
if (overrideNullPolicy().isPresent()) {
return overrideNullPolicy().get();
}
switch (kind()) {
case MODULE:
return componentCanMakeNewInstances(typeElement())
? NullPolicy.NEW
: requiresAPassedInstance() ? NullPolicy.THROW : NullPolicy.ALLOW;
case DEPENDENCY:
case BOUND_INSTANCE:
return NullPolicy.THROW;
}
throw new AssertionError();
}
/**
* Returns true if the passed {@link ComponentRequirement} requires a passed instance in order to
* be used within a component.
*/
public boolean requiresAPassedInstance() {
if (!kind().isModule()) {
// Bound instances and dependencies always require the user to provide an instance.
return true;
}
return requiresModuleInstance() && !componentCanMakeNewInstances(typeElement());
}
/**
* Returns {@code true} if an instance is needed for this (module) requirement.
*
* <p>An instance is only needed if there is a binding method on the module that is neither {@code
* abstract} nor {@code static}; if all bindings are one of those, then there should be no
* possible dependency on instance state in the module's bindings.
*
* <p>Alternatively, if the module is a Kotlin Object then the binding methods are considered
* {@code static}, requiring no module instance.
*/
private boolean requiresModuleInstance() {
if (typeElement().isKotlinObject() || typeElement().isCompanionObject()) {
return false;
}
return asStream(typeElement().getAllNonPrivateInstanceMethods())
.filter(this::isBindingMethod)
.anyMatch(method -> !method.isAbstract() && !method.isStatic());
}
private boolean isBindingMethod(XMethodElement method) {
// TODO(cgdecker): At the very least, we should have utility methods to consolidate this stuff
// in one place; listing individual annotations all over the place is brittle.
return hasAnyAnnotation(
method,
TypeNames.PROVIDES,
TypeNames.PRODUCES,
// TODO(ronshapiro): it would be cool to have internal meta-annotations that could describe
// these, like @AbstractBindingMethod
TypeNames.BINDS,
TypeNames.MULTIBINDS,
TypeNames.BINDS_OPTIONAL_OF);
}
/** The key for this requirement, if one is available. */
public abstract Optional<Key> key();
/** Returns the name for this requirement that could be used as a variable. */
public abstract String variableName();
/** Returns a parameter spec for this requirement. */
public ParameterSpec toParameterSpec() {
return ParameterSpec.builder(type().getTypeName(), variableName()).build();
}
public static ComponentRequirement forDependency(XType type) {
checkArgument(isDeclared(checkNotNull(type)));
ComponentRequirement requirement =
new AutoValue_ComponentRequirement(
Kind.DEPENDENCY,
type.getTypeName(),
Optional.empty(),
Optional.empty(),
simpleVariableName(type.getTypeElement().getClassName()));
requirement.type = type;
return requirement;
}
public static ComponentRequirement forModule(XType type) {
checkArgument(isDeclared(checkNotNull(type)));
ComponentRequirement requirement =
new AutoValue_ComponentRequirement(
Kind.MODULE,
type.getTypeName(),
Optional.empty(),
Optional.empty(),
simpleVariableName(type.getTypeElement().getClassName()));
requirement.type = type;
return requirement;
}
static ComponentRequirement forBoundInstance(
Key key, boolean nullable, XElement elementForVariableName) {
ComponentRequirement requirement =
new AutoValue_ComponentRequirement(
Kind.BOUND_INSTANCE,
key.type().xprocessing().getTypeName(),
nullable ? Optional.of(NullPolicy.ALLOW) : Optional.empty(),
Optional.of(key),
toJavac(elementForVariableName).getSimpleName().toString());
requirement.type = key.type().xprocessing();
return requirement;
}
public static ComponentRequirement forBoundInstance(ContributionBinding binding) {
checkArgument(binding.kind().equals(BindingKind.BOUND_INSTANCE));
ComponentRequirement requirement =
forBoundInstance(
binding.key(),
binding.nullableType().isPresent(),
binding.bindingElement().get());
requirement.type = binding.key().type().xprocessing();
return requirement;
}
/**
* Returns true if and only if a component can instantiate new instances (typically of a module)
* rather than requiring that they be passed.
*/
// TODO(bcorso): Should this method throw if its called knowing that an instance is not needed?
public static boolean componentCanMakeNewInstances(XTypeElement typeElement) {
// TODO(bcorso): Investigate how we should replace this in XProcessing. It's not clear what the
// complete set of kinds are in XProcessing and if they're mutually exclusive. For example,
// does XTypeElement#isClass() cover XTypeElement#isDataClass(), etc?
switch (toJavac(typeElement).getKind()) {
case CLASS:
break;
case ENUM:
case ANNOTATION_TYPE:
case INTERFACE:
return false;
default:
throw new AssertionError("TypeElement cannot have kind: " + toJavac(typeElement).getKind());
}
if (typeElement.isAbstract()) {
return false;
}
if (requiresEnclosingInstance(typeElement)) {
return false;
}
for (XElement enclosed : typeElement.getEnclosedElements()) {
if (isConstructor(enclosed)
&& asConstructor(enclosed).getParameters().isEmpty()
&& !asConstructor(enclosed).isPrivate()) {
return true;
}
}
// TODO(gak): still need checks for visibility
return false;
}
private static boolean requiresEnclosingInstance(XTypeElement typeElement) {
return isNested(typeElement) && !typeElement.isStatic();
}
}