/* * Copyright (C) 2014 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 com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.langmodel.DaggerTypes.isFutureType; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.type.TypeKind.VOID; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; import dagger.Component; import dagger.Module; import dagger.Subcomponent; import dagger.internal.codegen.base.ComponentAnnotation; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; import dagger.model.DependencyRequest; import dagger.model.Scope; import dagger.producers.CancellationPolicy; import dagger.producers.ProductionComponent; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; /** * A component declaration. * *

Represents one type annotated with {@code @Component}, {@code Subcomponent}, * {@code @ProductionComponent}, or {@code @ProductionSubcomponent}. * *

When validating bindings installed in modules, a {@link ComponentDescriptor} can also * represent a synthetic component for the module, where there is an entry point for each binding in * the module. */ @AutoValue public abstract class ComponentDescriptor { /** The annotation that specifies that {@link #typeElement()} is a component. */ public abstract ComponentAnnotation annotation(); /** Returns {@code true} if this is a subcomponent. */ public final boolean isSubcomponent() { return annotation().isSubcomponent(); } /** * Returns {@code true} if this is a production component or subcomponent, or a * {@code @ProducerModule} when doing module binding validation. */ public final boolean isProduction() { return annotation().isProduction(); } /** * Returns {@code true} if this is a real component, and not a fictional one used to validate * module bindings. */ public final boolean isRealComponent() { return annotation().isRealComponent(); } /** * The element that defines the component. This is the element to which the {@link #annotation()} * was applied. */ public abstract TypeElement typeElement(); /** * The set of component dependencies listed in {@link Component#dependencies} or {@link * ProductionComponent#dependencies()}. */ public abstract ImmutableSet dependencies(); /** The non-abstract {@link #modules()} and the {@link #dependencies()}. */ public final ImmutableSet dependenciesAndConcreteModules() { return Stream.concat( moduleTypes().stream() .filter(dep -> !dep.getModifiers().contains(ABSTRACT)) .map(module -> ComponentRequirement.forModule(module.asType())), dependencies().stream()) .collect(toImmutableSet()); } /** * The {@link ModuleDescriptor modules} declared in {@link Component#modules()} and reachable by * traversing {@link Module#includes()}. */ public abstract ImmutableSet modules(); /** The types of the {@link #modules()}. */ public final ImmutableSet moduleTypes() { return modules().stream().map(ModuleDescriptor::moduleElement).collect(toImmutableSet()); } /** * The types for which the component will need instances if all of its bindings are used. For the * types the component will need in a given binding graph, use {@link * BindingGraph#componentRequirements()}. * *

*/ @Memoized ImmutableSet requirements() { ImmutableSet.Builder requirements = ImmutableSet.builder(); modules().stream() .filter( module -> module.bindings().stream().anyMatch(ContributionBinding::requiresModuleInstance)) .map(module -> ComponentRequirement.forModule(module.moduleElement().asType())) .forEach(requirements::add); requirements.addAll(dependencies()); requirements.addAll( creatorDescriptor() .map(ComponentCreatorDescriptor::boundInstanceRequirements) .orElse(ImmutableSet.of())); return requirements.build(); } /** * This component's {@linkplain #dependencies() dependencies} keyed by each provision or * production method defined by that dependency. Note that the dependencies' types are not simply * the enclosing type of the method; a method may be declared by a supertype of the actual * dependency. */ public abstract ImmutableMap dependenciesByDependencyMethod(); /** The {@linkplain #dependencies() component dependency} that defines a method. */ public final ComponentRequirement getDependencyThatDefinesMethod(Element method) { checkArgument( method instanceof ExecutableElement, "method must be an executable element: %s", method); return checkNotNull( dependenciesByDependencyMethod().get(method), "no dependency implements %s", method); } /** The scopes of the component. */ public abstract ImmutableSet scopes(); /** * All {@link Subcomponent}s which are direct children of this component. This includes * subcomponents installed from {@link Module#subcomponents()} as well as subcomponent {@linkplain * #childComponentsDeclaredByFactoryMethods() factory methods} and {@linkplain * #childComponentsDeclaredByBuilderEntryPoints() builder methods}. */ public final ImmutableSet childComponents() { return ImmutableSet.builder() .addAll(childComponentsDeclaredByFactoryMethods().values()) .addAll(childComponentsDeclaredByBuilderEntryPoints().values()) .addAll(childComponentsDeclaredByModules()) .build(); } /** * All {@linkplain Subcomponent direct child} components that are declared by a {@linkplain * Module#subcomponents() module's subcomponents}. */ abstract ImmutableSet childComponentsDeclaredByModules(); /** * All {@linkplain Subcomponent direct child} components that are declared by a subcomponent * factory method. */ public abstract ImmutableBiMap childComponentsDeclaredByFactoryMethods(); /** Returns a map of {@link #childComponents()} indexed by {@link #typeElement()}. */ @Memoized public ImmutableMap childComponentsByElement() { return Maps.uniqueIndex(childComponents(), ComponentDescriptor::typeElement); } /** Returns the factory method that declares a child component. */ final Optional getFactoryMethodForChildComponent( ComponentDescriptor childComponent) { return Optional.ofNullable( childComponentsDeclaredByFactoryMethods().inverse().get(childComponent)); } /** * All {@linkplain Subcomponent direct child} components that are declared by a subcomponent * builder method. */ abstract ImmutableBiMap childComponentsDeclaredByBuilderEntryPoints(); private final Supplier> childComponentsByBuilderType = Suppliers.memoize( () -> childComponents().stream() .filter(child -> child.creatorDescriptor().isPresent()) .collect( toImmutableMap( child -> child.creatorDescriptor().get().typeElement(), child -> child))); /** Returns the child component with the given builder type. */ final ComponentDescriptor getChildComponentWithBuilderType(TypeElement builderType) { return checkNotNull( childComponentsByBuilderType.get().get(builderType), "no child component found for builder type %s", builderType.getQualifiedName()); } public abstract ImmutableSet componentMethods(); /** Returns the first component method associated with this binding request, if one exists. */ public Optional firstMatchingComponentMethod(BindingRequest request) { return Optional.ofNullable(firstMatchingComponentMethods().get(request)); } @Memoized ImmutableMap firstMatchingComponentMethods() { Map methods = new HashMap<>(); for (ComponentMethodDescriptor method : entryPointMethods()) { methods.putIfAbsent(BindingRequest.bindingRequest(method.dependencyRequest().get()), method); } return ImmutableMap.copyOf(methods); } /** The entry point methods on the component type. Each has a {@link DependencyRequest}. */ public final ImmutableSet entryPointMethods() { return componentMethods() .stream() .filter(method -> method.dependencyRequest().isPresent()) .collect(toImmutableSet()); } // TODO(gak): Consider making this non-optional and revising the // interaction between the spec & generation /** Returns a descriptor for the creator type for this component type, if the user defined one. */ public abstract Optional creatorDescriptor(); /** * Returns {@code true} for components that have a creator, either because the user {@linkplain * #creatorDescriptor() specified one} or because it's a top-level component with an implicit * builder. */ public final boolean hasCreator() { return !isSubcomponent() || creatorDescriptor().isPresent(); } /** * Returns the {@link CancellationPolicy} for this component, or an empty optional if either the * component is not a production component or no {@code CancellationPolicy} annotation is present. */ public final Optional cancellationPolicy() { return isProduction() ? Optional.ofNullable(typeElement().getAnnotation(CancellationPolicy.class)) : Optional.empty(); } @Memoized @Override public int hashCode() { // TODO(b/122962745): Only use typeElement().hashCode() return Objects.hash(typeElement(), annotation()); } // TODO(ronshapiro): simplify the equality semantics @Override public abstract boolean equals(Object obj); /** A component method. */ @AutoValue public abstract static class ComponentMethodDescriptor { /** The method itself. Note that this may be declared on a supertype of the component. */ public abstract ExecutableElement methodElement(); /** * The dependency request for production, provision, and subcomponent creator methods. Absent * for subcomponent factory methods. */ public abstract Optional dependencyRequest(); /** The subcomponent for subcomponent factory methods and subcomponent creator methods. */ public abstract Optional subcomponent(); /** * Returns the return type of {@link #methodElement()} as resolved in the {@link * ComponentDescriptor#typeElement() component type}. If there are no type variables in the * return type, this is the equivalent of {@code methodElement().getReturnType()}. */ public TypeMirror resolvedReturnType(DaggerTypes types) { checkState(dependencyRequest().isPresent()); TypeMirror returnType = methodElement().getReturnType(); if (returnType.getKind().isPrimitive() || returnType.getKind().equals(VOID)) { return returnType; } return BindingRequest.bindingRequest(dependencyRequest().get()) .requestedType(dependencyRequest().get().key().type(), types); } /** A {@link ComponentMethodDescriptor}builder for a method. */ public static Builder builder(ExecutableElement method) { return new AutoValue_ComponentDescriptor_ComponentMethodDescriptor.Builder() .methodElement(method); } /** A builder of {@link ComponentMethodDescriptor}s. */ @AutoValue.Builder @CanIgnoreReturnValue public interface Builder { /** @see ComponentMethodDescriptor#methodElement() */ Builder methodElement(ExecutableElement methodElement); /** @see ComponentMethodDescriptor#dependencyRequest() */ Builder dependencyRequest(DependencyRequest dependencyRequest); /** @see ComponentMethodDescriptor#subcomponent() */ Builder subcomponent(ComponentDescriptor subcomponent); /** Builds the descriptor. */ @CheckReturnValue ComponentMethodDescriptor build(); } } /** No-argument methods defined on {@link Object} that are ignored for contribution. */ private static final ImmutableSet NON_CONTRIBUTING_OBJECT_METHOD_NAMES = ImmutableSet.of("toString", "hashCode", "clone", "getClass"); /** * Returns {@code true} if a method could be a component entry point but not a members-injection * method. */ static boolean isComponentContributionMethod(DaggerElements elements, ExecutableElement method) { return method.getParameters().isEmpty() && !method.getReturnType().getKind().equals(VOID) && !elements.getTypeElement(Object.class).equals(method.getEnclosingElement()) && !NON_CONTRIBUTING_OBJECT_METHOD_NAMES.contains(method.getSimpleName().toString()); } /** Returns {@code true} if a method could be a component production entry point. */ static boolean isComponentProductionMethod(DaggerElements elements, ExecutableElement method) { return isComponentContributionMethod(elements, method) && isFutureType(method.getReturnType()); } }