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

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

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

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