All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dagger.internal.codegen.binding.ComponentDescriptor Maven / Gradle / Ivy

The newest version!
/*
 * 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 androidx.room.compiler.processing.XElementKt.isMethod;
import static androidx.room.compiler.processing.XTypeKt.isVoid;
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 com.google.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.base.ComponentAnnotation.rootComponentAnnotation;
import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation;
import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotations;
import static dagger.internal.codegen.base.ComponentCreatorAnnotation.creatorAnnotationsFor;
import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation;
import static dagger.internal.codegen.base.Scopes.productionScope;
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
import static dagger.internal.codegen.binding.ConfigurationAnnotations.enclosedAnnotatedTypes;
import static dagger.internal.codegen.binding.ConfigurationAnnotations.isSubcomponentCreator;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.javapoet.TypeNames.isFutureType;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
import static dagger.internal.codegen.xprocessing.XTypeElements.getAllUnimplementedMethods;
import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;

import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XMethodElement;
import androidx.room.compiler.processing.XMethodType;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
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.HashMultimap;
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 com.squareup.javapoet.TypeName;
import dagger.Component;
import dagger.Module;
import dagger.Subcomponent;
import dagger.internal.codegen.base.ClearableCache;
import dagger.internal.codegen.base.ComponentAnnotation;
import dagger.internal.codegen.base.DaggerSuperficialValidation;
import dagger.internal.codegen.base.ModuleAnnotation;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.internal.codegen.model.DependencyRequest;
import dagger.internal.codegen.model.Scope;
import dagger.internal.codegen.xprocessing.XTypeElements;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Singleton;

/**
 * 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. */ @CheckReturnValue @AutoValue public abstract class ComponentDescriptor { private BindingFactory bindingFactory; /** The annotation that specifies that {@link #typeElement()} is a component. */ public abstract ComponentAnnotation annotation(); /** * The element that defines the component. This is the element to which the {@link #annotation()} * was applied. */ public abstract XTypeElement typeElement(); /** * The set of component dependencies listed in {@link Component#dependencies} or {@link * dagger.producers.ProductionComponent#dependencies()}. */ public abstract ImmutableSet dependencies(); /** * The {@link ModuleDescriptor modules} declared in {@link Component#modules()} and reachable by * traversing {@link Module#includes()}. */ public abstract ImmutableSet modules(); /** The scopes of the component. */ public abstract ImmutableSet scopes(); /** * 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(); /** * All {@linkplain Subcomponent direct child} components that are declared by a subcomponent * builder method. */ abstract ImmutableMap childComponentsDeclaredByBuilderEntryPoints(); public abstract ImmutableSet componentMethods(); /** Returns a descriptor for the creator type for this component type, if the user defined one. */ public abstract Optional creatorDescriptor(); /** 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 non-abstract {@link #modules()} and the {@link #dependencies()}. */ public final ImmutableSet dependenciesAndConcreteModules() { return Stream.concat( moduleTypes().stream() .filter(dep -> !dep.isAbstract()) .map(module -> ComponentRequirement.forModule(module.getType())), dependencies().stream()) .collect(toImmutableSet()); } /** 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()}. * *

    *
  • {@linkplain #modules()} modules} with concrete instance bindings *
  • Bound instances *
  • {@linkplain #dependencies() dependencies} *
*/ @Memoized ImmutableSet requirements() { ImmutableSet.Builder requirements = ImmutableSet.builder(); modules().stream() .filter( module -> module.bindings().stream().anyMatch(ContributionBinding::requiresModuleInstance)) .map(module -> ComponentRequirement.forModule(module.moduleElement().getType())) .forEach(requirements::add); requirements.addAll(dependencies()); requirements.addAll( creatorDescriptor() .map(ComponentCreatorDescriptor::boundInstanceRequirements) .orElse(ImmutableSet.of())); return requirements.build(); } /** * Returns this component's dependencies keyed by its provision/production method. * *

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. */ @Memoized public ImmutableMap dependenciesByDependencyMethod() { ImmutableMap.Builder builder = ImmutableMap.builder(); for (ComponentRequirement componentDependency : dependencies()) { XTypeElements.getAllMethods(componentDependency.typeElement()).stream() .filter(ComponentDescriptor::isComponentContributionMethod) .forEach(method -> builder.put(method, componentDependency)); } return builder.buildOrThrow(); } /** The {@linkplain #dependencies() component dependency} that defines a method. */ public final ComponentRequirement getDependencyThatDefinesMethod(XElement method) { checkArgument(isMethod(method), "method must be an executable element: %s", method); checkState( dependenciesByDependencyMethod().containsKey(method), "no dependency implements %s", method); return dependenciesByDependencyMethod().get(method); } /** * 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(); } /** 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)); } 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(XTypeElement builderType) { return checkNotNull( childComponentsByBuilderType.get().get(builderType), "no child component found for builder type %s", builderType.getQualifiedName()); } /** 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()); } /** * 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() // TODO(bcorso): Get values from XAnnotation instead of using CancellationPolicy directly. ? Optional.ofNullable(typeElement().getAnnotation(TypeNames.CANCELLATION_POLICY)) .map(CancellationPolicy::from) : Optional.empty(); } /** Returns the bindings for the component. */ @Memoized public ImmutableSet bindings() { ImmutableSet.Builder builder = ImmutableSet.builder(); componentBinding().ifPresent(builder::add); return builder .addAll(componentDependencyBindings()) .addAll(boundInstanceBindings()) .addAll(subcomponentCreatorBindings()) .addAll(moduleBindings()) .build(); } /** Returns the binding for the component, itself, if this is a real component. */ @Memoized Optional componentBinding() { return isRealComponent() ? Optional.of(bindingFactory.componentBinding(typeElement())) : Optional.empty(); } /** Returns the bindings for the component dependency and those contributed by its methods. */ @Memoized ImmutableSet componentDependencyBindings() { ImmutableSet.Builder builder = ImmutableSet.builder(); for (ComponentRequirement dependency : dependencies()) { builder.add(bindingFactory.componentDependencyBinding(dependency)); // Within a component dependency, we want to allow the same method to appear multiple // times assuming it is the exact same method. We do this by tracking a set of bindings // we've already added with the binding element removed since that is the only thing // allowed to differ. HashMultimap dedupeBindings = HashMultimap.create(); XTypeElements.getAllMethods(dependency.typeElement()).stream() // MembersInjection methods aren't "provided" explicitly, so ignore them. .filter(ComponentDescriptor::isComponentContributionMethod) .forEach( method -> { ContributionBinding binding = isProduction() && isComponentProductionMethod(method) ? bindingFactory.componentDependencyProductionMethodBinding(method) : bindingFactory.componentDependencyProvisionMethodBinding(method); if (dedupeBindings.put( getSimpleName(method), // Remove the binding element since we know that will be different, but // everything else we want to be the same to consider it a duplicate. binding.toBuilder().clearBindingElement().build())) { builder.add(binding); } }); } return builder.build(); } /** Returns the {@code @BindsInstance} bindings required to create this component. */ @Memoized ImmutableSet boundInstanceBindings() { return creatorDescriptor().isPresent() ? creatorDescriptor().get().boundInstanceRequirements().stream() .map( requirement -> bindingFactory.boundInstanceBinding( requirement, creatorDescriptor().get().elementForRequirement(requirement))) .collect(toImmutableSet()) : ImmutableSet.of(); } /** Returns the subcomponent creator bindings for this component. */ @Memoized ImmutableSet subcomponentCreatorBindings() { ImmutableSet.Builder builder = ImmutableSet.builder(); childComponentsDeclaredByBuilderEntryPoints() .forEach( (builderEntryPoint, childComponent) -> { if (!childComponentsDeclaredByModules().contains(childComponent)) { builder.add( bindingFactory.subcomponentCreatorBinding( builderEntryPoint.methodElement(), typeElement())); } }); return builder.build(); } @Memoized ImmutableSet moduleBindings() { return modules().stream() .map(ModuleDescriptor::bindings) .flatMap(ImmutableSet::stream) .collect(toImmutableSet()); } @Memoized public ImmutableSet delegateDeclarations() { return modules().stream() .map(ModuleDescriptor::delegateDeclarations) .flatMap(ImmutableSet::stream) .collect(toImmutableSet()); } @Memoized public ImmutableSet multibindingDeclarations() { return modules().stream() .map(ModuleDescriptor::multibindingDeclarations) .flatMap(ImmutableSet::stream) .collect(toImmutableSet()); } @Memoized public ImmutableSet optionalBindingDeclarations() { return modules().stream() .map(ModuleDescriptor::optionalDeclarations) .flatMap(ImmutableSet::stream) .collect(toImmutableSet()); } @Memoized public ImmutableSet subcomponentDeclarations() { return modules().stream() .map(ModuleDescriptor::subcomponentDeclarations) .flatMap(ImmutableSet::stream) .collect(toImmutableSet()); } @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 XMethodElement 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(); /** A {@link ComponentMethodDescriptor}builder for a method. */ public static Builder builder(XMethodElement method) { return new AutoValue_ComponentDescriptor_ComponentMethodDescriptor.Builder() .methodElement(method); } /** A builder of {@link ComponentMethodDescriptor}s. */ @AutoValue.Builder public interface Builder { /** @see ComponentMethodDescriptor#methodElement() */ Builder methodElement(XMethodElement methodElement); /** * @see ComponentMethodDescriptor#dependencyRequest() */ @CanIgnoreReturnValue // TODO(kak): remove this once open-source checkers understand AutoValue Builder dependencyRequest(DependencyRequest dependencyRequest); /** * @see ComponentMethodDescriptor#subcomponent() */ @CanIgnoreReturnValue // TODO(kak): remove this once open-source checkers understand AutoValue Builder subcomponent(ComponentDescriptor subcomponent); /** Builds the descriptor. */ 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. */ private static boolean isComponentContributionMethod(XMethodElement method) { return method.getParameters().isEmpty() && !isVoid(method.getReturnType()) && !method.getEnclosingElement().getClassName().equals(TypeName.OBJECT) && !NON_CONTRIBUTING_OBJECT_METHOD_NAMES.contains(getSimpleName(method)); } /** Returns {@code true} if a method could be a component production entry point. */ private static boolean isComponentProductionMethod(XMethodElement method) { return isComponentContributionMethod(method) && isFutureType(method.getReturnType()); } /** A factory for creating a {@link ComponentDescriptor}. */ @Singleton public static final class Factory implements ClearableCache { private final XProcessingEnv processingEnv; private final BindingFactory bindingFactory; private final DependencyRequestFactory dependencyRequestFactory; private final ModuleDescriptor.Factory moduleDescriptorFactory; private final InjectionAnnotations injectionAnnotations; private final DaggerSuperficialValidation superficialValidation; private final Map cache = new HashMap<>(); @Inject Factory( XProcessingEnv processingEnv, BindingFactory bindingFactory, DependencyRequestFactory dependencyRequestFactory, ModuleDescriptor.Factory moduleDescriptorFactory, InjectionAnnotations injectionAnnotations, DaggerSuperficialValidation superficialValidation) { this.processingEnv = processingEnv; this.bindingFactory = bindingFactory; this.dependencyRequestFactory = dependencyRequestFactory; this.moduleDescriptorFactory = moduleDescriptorFactory; this.injectionAnnotations = injectionAnnotations; this.superficialValidation = superficialValidation; } /** Returns a descriptor for a root component type. */ public ComponentDescriptor rootComponentDescriptor(XTypeElement typeElement) { Optional annotation = rootComponentAnnotation(typeElement, superficialValidation); checkArgument(annotation.isPresent(), "%s must have a component annotation", typeElement); return create(typeElement, annotation.get()); } /** Returns a descriptor for a subcomponent type. */ public ComponentDescriptor subcomponentDescriptor(XTypeElement typeElement) { Optional annotation = subcomponentAnnotation(typeElement, superficialValidation); checkArgument(annotation.isPresent(), "%s must have a subcomponent annotation", typeElement); return create(typeElement, annotation.get()); } /** * Returns a descriptor for a fictional component based on a module type in order to validate * its bindings. */ public ComponentDescriptor moduleComponentDescriptor(XTypeElement typeElement) { Optional annotation = moduleAnnotation(typeElement, superficialValidation); checkArgument(annotation.isPresent(), "%s must have a module annotation", typeElement); return create(typeElement, ComponentAnnotation.fromModuleAnnotation(annotation.get())); } private ComponentDescriptor create( XTypeElement typeElement, ComponentAnnotation componentAnnotation) { return reentrantComputeIfAbsent( cache, typeElement, unused -> createUncached(typeElement, componentAnnotation)); } private ComponentDescriptor createUncached( XTypeElement typeElement, ComponentAnnotation componentAnnotation) { ImmutableSet componentDependencies = componentAnnotation.dependencyTypes().stream() .map(ComponentRequirement::forDependency) .collect(toImmutableSet()); // Start with the component's modules. For fictional components built from a module, start // with that module. ImmutableSet modules = componentAnnotation.isRealComponent() ? componentAnnotation.modules() : ImmutableSet.of(typeElement); ImmutableSet transitiveModules = moduleDescriptorFactory.transitiveModules(modules); ImmutableSet.Builder componentMethodsBuilder = ImmutableSet.builder(); ImmutableBiMap.Builder subcomponentsByFactoryMethod = ImmutableBiMap.builder(); ImmutableMap.Builder subcomponentsByBuilderMethod = ImmutableBiMap.builder(); if (componentAnnotation.isRealComponent()) { for (XMethodElement componentMethod : getAllUnimplementedMethods(typeElement)) { ComponentMethodDescriptor componentMethodDescriptor = getDescriptorForComponentMethod(componentAnnotation, typeElement, componentMethod); componentMethodsBuilder.add(componentMethodDescriptor); componentMethodDescriptor .subcomponent() .ifPresent( subcomponent -> { // If the dependency request is present, that means the method returns the // subcomponent factory. if (componentMethodDescriptor.dependencyRequest().isPresent()) { subcomponentsByBuilderMethod.put(componentMethodDescriptor, subcomponent); } else { subcomponentsByFactoryMethod.put(componentMethodDescriptor, subcomponent); } }); } } // Validation should have ensured that this set will have at most one element. ImmutableSet enclosedCreators = enclosedAnnotatedTypes(typeElement, creatorAnnotationsFor(componentAnnotation)); Optional creatorDescriptor = enclosedCreators.isEmpty() ? Optional.empty() : Optional.of( ComponentCreatorDescriptor.create( getOnlyElement(enclosedCreators), dependencyRequestFactory)); ImmutableSet scopes = injectionAnnotations.getScopes(typeElement); if (componentAnnotation.isProduction()) { scopes = ImmutableSet.builder() .addAll(scopes).add(productionScope(processingEnv)) .build(); } ImmutableSet subcomponentsFromModules = transitiveModules.stream() .flatMap(transitiveModule -> transitiveModule.subcomponentDeclarations().stream()) .map(SubcomponentDeclaration::subcomponentType) .map(this::subcomponentDescriptor) .collect(toImmutableSet()); ComponentDescriptor componentDescriptor = new AutoValue_ComponentDescriptor( componentAnnotation, typeElement, componentDependencies, transitiveModules, scopes, subcomponentsFromModules, subcomponentsByFactoryMethod.buildOrThrow(), subcomponentsByBuilderMethod.buildOrThrow(), componentMethodsBuilder.build(), creatorDescriptor); componentDescriptor.bindingFactory = bindingFactory; return componentDescriptor; } private ComponentMethodDescriptor getDescriptorForComponentMethod( ComponentAnnotation componentAnnotation, XTypeElement componentElement, XMethodElement componentMethod) { ComponentMethodDescriptor.Builder descriptor = ComponentMethodDescriptor.builder(componentMethod); XMethodType resolvedComponentMethod = componentMethod.asMemberOf(componentElement.getType()); XType returnType = resolvedComponentMethod.getReturnType(); if (isDeclared(returnType) && !injectionAnnotations.getQualifier(componentMethod).isPresent()) { XTypeElement returnTypeElement = returnType.getTypeElement(); if (returnTypeElement.hasAnyAnnotation(subcomponentAnnotations())) { // It's a subcomponent factory method. There is no dependency request, and there could be // any number of parameters. Just return the descriptor. return descriptor.subcomponent(subcomponentDescriptor(returnTypeElement)).build(); } if (isSubcomponentCreator(returnTypeElement)) { descriptor.subcomponent( subcomponentDescriptor(returnTypeElement.getEnclosingTypeElement())); } } switch (componentMethod.getParameters().size()) { case 0: checkArgument( !isVoid(returnType), "component method cannot be void: %s", componentMethod); descriptor.dependencyRequest( componentAnnotation.isProduction() ? dependencyRequestFactory.forComponentProductionMethod( componentMethod, resolvedComponentMethod) : dependencyRequestFactory.forComponentProvisionMethod( componentMethod, resolvedComponentMethod)); break; case 1: checkArgument( isVoid(returnType) // TODO(bcorso): Replace this with isSameType()? || returnType .getTypeName() .equals(resolvedComponentMethod.getParameterTypes().get(0).getTypeName()), "members injection method must return void or parameter type: %s", componentMethod); descriptor.dependencyRequest( dependencyRequestFactory.forComponentMembersInjectionMethod( componentMethod, resolvedComponentMethod)); break; default: throw new IllegalArgumentException( "component method has too many parameters: " + componentMethod); } return descriptor.build(); } @Override public void clearCache() { cache.clear(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy