| /* |
| * 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.writing; |
| |
| import static androidx.room.compiler.processing.compat.XConverters.toJavac; |
| import static com.google.auto.common.MoreTypes.asDeclared; |
| import static com.google.common.base.CaseFormat.LOWER_CAMEL; |
| import static com.google.common.base.CaseFormat.UPPER_CAMEL; |
| import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkState; |
| import static com.squareup.javapoet.MethodSpec.constructorBuilder; |
| import static com.squareup.javapoet.MethodSpec.methodBuilder; |
| import static com.squareup.javapoet.TypeSpec.classBuilder; |
| import static dagger.internal.codegen.binding.ComponentCreatorKind.BUILDER; |
| import static dagger.internal.codegen.binding.SourceFiles.simpleVariableName; |
| import static dagger.internal.codegen.extension.DaggerStreams.instancesOf; |
| import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; |
| import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; |
| import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; |
| import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings; |
| import static dagger.internal.codegen.javapoet.CodeBlocks.parameterNames; |
| import static dagger.internal.codegen.langmodel.Accessibility.isProtectedMemberOf; |
| import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; |
| import static dagger.internal.codegen.writing.ComponentImplementation.MethodSpecKind.COMPONENT_METHOD; |
| import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; |
| import static dagger.producers.CancellationPolicy.Propagation.PROPAGATE; |
| import static javax.lang.model.element.Modifier.FINAL; |
| import static javax.lang.model.element.Modifier.PRIVATE; |
| import static javax.lang.model.element.Modifier.PUBLIC; |
| import static javax.lang.model.element.Modifier.STATIC; |
| import static javax.tools.Diagnostic.Kind.ERROR; |
| |
| import androidx.room.compiler.processing.XMessager; |
| import androidx.room.compiler.processing.XType; |
| import androidx.room.compiler.processing.XTypeElement; |
| import androidx.room.compiler.processing.compat.XConverters; |
| import com.google.auto.common.MoreElements; |
| import com.google.auto.common.MoreTypes; |
| import com.google.common.base.Function; |
| import com.google.common.base.Supplier; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.ListMultimap; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.MultimapBuilder; |
| import com.google.common.collect.Sets; |
| import com.squareup.javapoet.ClassName; |
| import com.squareup.javapoet.CodeBlock; |
| import com.squareup.javapoet.FieldSpec; |
| import com.squareup.javapoet.MethodSpec; |
| import com.squareup.javapoet.ParameterSpec; |
| import com.squareup.javapoet.TypeName; |
| import com.squareup.javapoet.TypeSpec; |
| import dagger.internal.Preconditions; |
| import dagger.internal.codegen.base.UniqueNameSet; |
| import dagger.internal.codegen.binding.Binding; |
| import dagger.internal.codegen.binding.BindingGraph; |
| import dagger.internal.codegen.binding.BindingNode; |
| import dagger.internal.codegen.binding.BindingRequest; |
| import dagger.internal.codegen.binding.ComponentCreatorDescriptor; |
| import dagger.internal.codegen.binding.ComponentCreatorKind; |
| import dagger.internal.codegen.binding.ComponentDescriptor; |
| import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor; |
| import dagger.internal.codegen.binding.ComponentRequirement; |
| import dagger.internal.codegen.binding.KeyVariableNamer; |
| import dagger.internal.codegen.binding.MethodSignature; |
| import dagger.internal.codegen.compileroption.CompilerOptions; |
| import dagger.internal.codegen.javapoet.CodeBlocks; |
| import dagger.internal.codegen.javapoet.TypeNames; |
| import dagger.internal.codegen.javapoet.TypeSpecs; |
| import dagger.internal.codegen.langmodel.DaggerElements; |
| import dagger.internal.codegen.langmodel.DaggerTypes; |
| import dagger.spi.model.BindingGraph.Node; |
| import dagger.spi.model.Key; |
| import dagger.spi.model.RequestKind; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import javax.inject.Inject; |
| import javax.inject.Provider; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.Modifier; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.TypeMirror; |
| |
| /** The implementation of a component type. */ |
| @PerComponentImplementation |
| public final class ComponentImplementation { |
| /** A factory for creating a {@link ComponentImplementation}. */ |
| public interface ChildComponentImplementationFactory { |
| /** Creates a {@link ComponentImplementation} for the given {@code childGraph}. */ |
| ComponentImplementation create(BindingGraph childGraph); |
| } |
| |
| /** Compiler Modes. */ |
| public enum CompilerMode { |
| DEFAULT, |
| FAST_INIT, |
| EXPERIMENTAL_MERGED_MODE; |
| |
| public boolean isFastInit() { |
| return this == CompilerMode.FAST_INIT; |
| } |
| |
| public boolean isExperimentalMergedMode() { |
| return this == CompilerMode.EXPERIMENTAL_MERGED_MODE; |
| } |
| } |
| |
| /** A type of field that this component can contain. */ |
| public enum FieldSpecKind { |
| /** A field for a component shard. */ |
| COMPONENT_SHARD_FIELD, |
| |
| /** A field required by the component, e.g. module instances. */ |
| COMPONENT_REQUIREMENT_FIELD, |
| |
| /** A framework field for type T, e.g. {@code Provider<T>}. */ |
| FRAMEWORK_FIELD, |
| |
| /** A static field that always returns an absent {@code Optional} value for the binding. */ |
| ABSENT_OPTIONAL_FIELD |
| } |
| |
| /** A type of method that this component can contain. */ |
| // TODO(bcorso, dpb): Change the oder to constructor, initialize, component, then private |
| // (including MIM and AOM—why treat those separately?). |
| public enum MethodSpecKind { |
| /** The component constructor. */ |
| CONSTRUCTOR, |
| |
| /** A builder method for the component. (Only used by the root component.) */ |
| BUILDER_METHOD, |
| |
| /** A private method that wraps dependency expressions. */ |
| PRIVATE_METHOD, |
| |
| /** An initialization method that initializes component requirements and framework types. */ |
| INITIALIZE_METHOD, |
| |
| /** An implementation of a component interface method. */ |
| COMPONENT_METHOD, |
| |
| /** A private method that encapsulates members injection logic for a binding. */ |
| MEMBERS_INJECTION_METHOD, |
| |
| /** A static method that always returns an absent {@code Optional} value for the binding. */ |
| ABSENT_OPTIONAL_METHOD, |
| |
| /** |
| * The {@link dagger.producers.internal.CancellationListener#onProducerFutureCancelled(boolean)} |
| * method for a production component. |
| */ |
| CANCELLATION_LISTENER_METHOD |
| } |
| |
| /** A type of nested class that this component can contain. */ |
| public enum TypeSpecKind { |
| /** A factory class for a present optional binding. */ |
| PRESENT_FACTORY, |
| |
| /** A class for the component creator (only used by the root component.) */ |
| COMPONENT_CREATOR, |
| |
| /** A provider class for a component provision. */ |
| COMPONENT_PROVISION_FACTORY, |
| |
| /** A class for a component shard. */ |
| COMPONENT_SHARD_TYPE, |
| |
| /** A class for the subcomponent or subcomponent builder. */ |
| SUBCOMPONENT |
| } |
| |
| /** |
| * Returns the {@link ShardImplementation} for each binding in this graph. |
| * |
| * <p>Each shard contains approximately {@link CompilerOptions#keysPerComponentShard()} bindings. |
| * |
| * <p>If more than 1 shard is needed, we iterate the strongly connected nodes to make sure of two |
| * things: 1) bindings are put in shards in reverse topological order (i.e., bindings in Shard{i} |
| * do not depend on bindings in Shard{i+j}) and 2) bindings belonging to the same cycle are put in |
| * the same shard. These two guarantees allow us to initialize each shard in a well defined order. |
| */ |
| private static ImmutableMap<Binding, ShardImplementation> createShardsByBinding( |
| ShardImplementation componentShard, BindingGraph graph, CompilerOptions compilerOptions) { |
| ImmutableList<ImmutableList<Binding>> partitions = bindingPartitions(graph, compilerOptions); |
| ImmutableMap.Builder<Binding, ShardImplementation> builder = ImmutableMap.builder(); |
| for (int i = 0; i < partitions.size(); i++) { |
| ShardImplementation shard = i == 0 ? componentShard : componentShard.createShard("Shard" + i); |
| partitions.get(i).forEach(binding -> builder.put(binding, shard)); |
| } |
| return builder.build(); |
| } |
| |
| private static ImmutableList<ImmutableList<Binding>> bindingPartitions( |
| BindingGraph graph, CompilerOptions compilerOptions) { |
| int bindingsPerShard = compilerOptions.keysPerComponentShard(graph.componentTypeElement()); |
| int maxPartitions = (graph.localBindingNodes().size() / bindingsPerShard) + 1; |
| if (maxPartitions <= 1) { |
| return ImmutableList.of( |
| graph.localBindingNodes().stream().map(BindingNode::delegate).collect(toImmutableList())); |
| } |
| |
| // Iterate through all SCCs in order until all bindings local to this component are partitioned. |
| List<Binding> currPartition = new ArrayList<>(bindingsPerShard); |
| ImmutableList.Builder<ImmutableList<Binding>> partitions = |
| ImmutableList.builderWithExpectedSize(maxPartitions); |
| for (ImmutableSet<Node> nodes : graph.topLevelBindingGraph().stronglyConnectedNodes()) { |
| nodes.stream() |
| .flatMap(instancesOf(BindingNode.class)) |
| .filter(bindingNode -> bindingNode.componentPath().equals(graph.componentPath())) |
| .map(BindingNode::delegate) |
| .forEach(currPartition::add); |
| if (currPartition.size() >= bindingsPerShard) { |
| partitions.add(ImmutableList.copyOf(currPartition)); |
| currPartition = new ArrayList<>(bindingsPerShard); |
| } |
| } |
| if (!currPartition.isEmpty()) { |
| partitions.add(ImmutableList.copyOf(currPartition)); |
| } |
| return partitions.build(); |
| } |
| |
| /** The boolean parameter of the onProducerFutureCancelled method. */ |
| public static final ParameterSpec MAY_INTERRUPT_IF_RUNNING_PARAM = |
| ParameterSpec.builder(boolean.class, "mayInterruptIfRunning").build(); |
| |
| private static final String CANCELLATION_LISTENER_METHOD_NAME = "onProducerFutureCancelled"; |
| |
| /** |
| * How many statements per {@code initialize()} or {@code onProducerFutureCancelled()} method |
| * before they get partitioned. |
| */ |
| private static final int STATEMENTS_PER_METHOD = 100; |
| |
| private final ShardImplementation componentShard; |
| private final ImmutableMap<Binding, ShardImplementation> shardsByBinding; |
| private final Map<ShardImplementation, FieldSpec> shardFieldsByImplementation = new HashMap<>(); |
| private final List<CodeBlock> shardInitializations = new ArrayList<>(); |
| private final List<CodeBlock> shardCancellations = new ArrayList<>(); |
| private final Optional<ComponentImplementation> parent; |
| private final ChildComponentImplementationFactory childComponentImplementationFactory; |
| private final Provider<ComponentRequestRepresentations> bindingExpressionsProvider; |
| private final Provider<ComponentCreatorImplementationFactory> |
| componentCreatorImplementationFactoryProvider; |
| private final BindingGraph graph; |
| private final ComponentNames componentNames; |
| private final DaggerElements elements; |
| private final DaggerTypes types; |
| private final ImmutableMap<ComponentImplementation, FieldSpec> componentFieldsByImplementation; |
| private final XMessager messager; |
| private final CompilerMode compilerMode; |
| |
| @Inject |
| ComponentImplementation( |
| @ParentComponent Optional<ComponentImplementation> parent, |
| ChildComponentImplementationFactory childComponentImplementationFactory, |
| // Inject as Provider<> to prevent a cycle. |
| Provider<ComponentRequestRepresentations> bindingExpressionsProvider, |
| Provider<ComponentCreatorImplementationFactory> componentCreatorImplementationFactoryProvider, |
| BindingGraph graph, |
| ComponentNames componentNames, |
| CompilerOptions compilerOptions, |
| DaggerElements elements, |
| DaggerTypes types, |
| XMessager messager) { |
| this.parent = parent; |
| this.childComponentImplementationFactory = childComponentImplementationFactory; |
| this.bindingExpressionsProvider = bindingExpressionsProvider; |
| this.componentCreatorImplementationFactoryProvider = |
| componentCreatorImplementationFactoryProvider; |
| this.graph = graph; |
| this.componentNames = componentNames; |
| this.elements = elements; |
| this.types = types; |
| |
| // The first group of keys belong to the component itself. We call this the componentShard. |
| this.componentShard = new ShardImplementation(componentNames.get(graph.componentPath())); |
| |
| // Claim the method names for all local and inherited methods on the component type. |
| elements |
| .getLocalAndInheritedMethods(toJavac(graph.componentTypeElement())) |
| .forEach(method -> componentShard.componentMethodNames.claim(method.getSimpleName())); |
| |
| // Create the shards for this component, indexed by binding. |
| this.shardsByBinding = createShardsByBinding(componentShard, graph, compilerOptions); |
| |
| // Create and claim the fields for this and all ancestor components stored as fields. |
| this.componentFieldsByImplementation = |
| createComponentFieldsByImplementation(this, compilerOptions); |
| this.messager = messager; |
| XTypeElement typeElement = rootComponentImplementation().componentDescriptor().typeElement(); |
| this.compilerMode = |
| compilerOptions.fastInit(typeElement) |
| ? CompilerMode.FAST_INIT |
| : (compilerOptions.experimentalMergedMode(typeElement) |
| ? CompilerMode.EXPERIMENTAL_MERGED_MODE |
| : CompilerMode.DEFAULT); |
| } |
| |
| /** |
| * Returns the shard for a given {@link Binding}. |
| * |
| * <p>Each set of {@link CompilerOptions#keysPerShard()} will get its own shard instance. |
| */ |
| public ShardImplementation shardImplementation(Binding binding) { |
| checkState(shardsByBinding.containsKey(binding), "No shard in %s for: %s", name(), binding); |
| return shardsByBinding.get(binding); |
| } |
| |
| /** Returns the root {@link ComponentImplementation}. */ |
| ComponentImplementation rootComponentImplementation() { |
| return parent.map(ComponentImplementation::rootComponentImplementation).orElse(this); |
| } |
| |
| /** Returns a reference to this implementation when called from a different class. */ |
| public CodeBlock componentFieldReference() { |
| // TODO(bcorso): This currently relies on all requesting classes having a reference to the |
| // component with the same name, which is kind of sketchy. Try to think of a better way that |
| // can accomodate the component missing in some classes if it's not used. |
| return CodeBlock.of("$N", componentFieldsByImplementation.get(this)); |
| } |
| |
| /** Returns the fields for all components in the component path. */ |
| public ImmutableList<FieldSpec> componentFields() { |
| return ImmutableList.copyOf(componentFieldsByImplementation.values()); |
| } |
| |
| /** Returns the fields for all components in the component path except the current component. */ |
| public ImmutableList<FieldSpec> creatorComponentFields() { |
| return componentFieldsByImplementation.entrySet().stream() |
| .filter(entry -> !this.equals(entry.getKey())) |
| .map(Map.Entry::getValue) |
| .collect(toImmutableList()); |
| } |
| |
| private static ImmutableMap<ComponentImplementation, FieldSpec> |
| createComponentFieldsByImplementation( |
| ComponentImplementation componentImplementation, CompilerOptions compilerOptions) { |
| checkArgument( |
| componentImplementation.componentShard != null, |
| "The component shard must be set before computing the component fields."); |
| ImmutableList.Builder<ComponentImplementation> builder = ImmutableList.builder(); |
| for (ComponentImplementation curr = componentImplementation; |
| curr != null; |
| curr = curr.parent.orElse(null)) { |
| builder.add(curr); |
| } |
| // For better readability when adding these fields/parameters to generated code, we collect the |
| // component implementations in reverse order so that parents appear before children. |
| return builder.build().reverse().stream() |
| .collect( |
| toImmutableMap( |
| componentImpl -> componentImpl, |
| componentImpl -> { |
| ClassName component = |
| componentImpl.graph.componentPath().currentComponent().className(); |
| ClassName fieldType = componentImpl.name(); |
| String fieldName = |
| componentImpl.isNested() |
| ? simpleVariableName(componentImpl.name()) |
| : simpleVariableName(component); |
| FieldSpec.Builder field = FieldSpec.builder(fieldType, fieldName, PRIVATE, FINAL); |
| componentImplementation.componentShard.componentFieldNames.claim(fieldName); |
| |
| return field.build(); |
| })); |
| } |
| /** Returns the shard representing the {@link ComponentImplementation} itself. */ |
| public ShardImplementation getComponentShard() { |
| return componentShard; |
| } |
| |
| /** Returns the binding graph for the component being generated. */ |
| public BindingGraph graph() { |
| return componentShard.graph(); |
| } |
| |
| /** Returns the descriptor for the component being generated. */ |
| public ComponentDescriptor componentDescriptor() { |
| return componentShard.componentDescriptor(); |
| } |
| |
| /** Returns the name of the component. */ |
| public ClassName name() { |
| return componentShard.name; |
| } |
| |
| /** Returns if the current compile mode is fast init. */ |
| public CompilerMode compilerMode() { |
| return compilerMode; |
| } |
| |
| /** Returns whether or not the implementation is nested within another class. */ |
| private boolean isNested() { |
| return name().enclosingClassName() != null; |
| } |
| |
| /** |
| * Returns the name of the creator class for this component. It will be a sibling of this |
| * generated class unless this is a top-level component, in which case it will be nested. |
| */ |
| public ClassName getCreatorName() { |
| return componentNames.getCreatorName(graph.componentPath()); |
| } |
| |
| /** Generates the component and returns the resulting {@link TypeSpec}. */ |
| public TypeSpec generate() { |
| return componentShard.generate(); |
| } |
| |
| /** |
| * The implementation of a shard. |
| * |
| * <p>The purpose of a shard is to allow a component implemenation to be split into multiple |
| * classes, where each class owns the creation logic for a set of keys. Sharding is useful for |
| * large component implementations, where a single component implementation class can reach size |
| * limitations, such as the constant pool size. |
| * |
| * <p>When generating the actual sources, the creation logic within the first instance of {@link |
| * ShardImplementation} will go into the component implementation class itself (e.g. {@code |
| * MySubcomponentImpl}). Each subsequent instance of {@link ShardImplementation} will generate a |
| * nested "shard" class within the component implementation (e.g. {@code |
| * MySubcomponentImpl.Shard1}, {@code MySubcomponentImpl.Shard2}, etc). |
| */ |
| public final class ShardImplementation { |
| private final ClassName name; |
| private final UniqueNameSet componentFieldNames = new UniqueNameSet(); |
| private final UniqueNameSet componentMethodNames = new UniqueNameSet(); |
| private final UniqueNameSet componentClassNames = new UniqueNameSet(); |
| private final UniqueNameSet assistedParamNames = new UniqueNameSet(); |
| private final List<CodeBlock> initializations = new ArrayList<>(); |
| private final Map<Key, CodeBlock> cancellations = new LinkedHashMap<>(); |
| private final Map<VariableElement, String> uniqueAssistedName = new LinkedHashMap<>(); |
| private final List<CodeBlock> componentRequirementInitializations = new ArrayList<>(); |
| private final ImmutableMap<ComponentRequirement, ParameterSpec> constructorParameters; |
| private final ListMultimap<FieldSpecKind, FieldSpec> fieldSpecsMap = |
| MultimapBuilder.enumKeys(FieldSpecKind.class).arrayListValues().build(); |
| private final ListMultimap<MethodSpecKind, MethodSpec> methodSpecsMap = |
| MultimapBuilder.enumKeys(MethodSpecKind.class).arrayListValues().build(); |
| private final ListMultimap<TypeSpecKind, TypeSpec> typeSpecsMap = |
| MultimapBuilder.enumKeys(TypeSpecKind.class).arrayListValues().build(); |
| private final List<Supplier<TypeSpec>> typeSuppliers = new ArrayList<>(); |
| private boolean initialized = false; // This is used for initializing assistedParamNames. |
| |
| private ShardImplementation(ClassName name) { |
| this.name = name; |
| if (graph.componentDescriptor().isProduction()) { |
| claimMethodName(CANCELLATION_LISTENER_METHOD_NAME); |
| } |
| |
| // Build the map of constructor parameters for this shard and claim the field names to prevent |
| // collisions between the constructor parameters and fields. |
| constructorParameters = |
| constructorRequirements(graph).stream() |
| .collect( |
| toImmutableMap( |
| requirement -> requirement, |
| requirement -> |
| ParameterSpec.builder( |
| requirement.type().getTypeName(), |
| getUniqueFieldName(requirement.variableName() + "Param")) |
| .build())); |
| } |
| |
| private ShardImplementation createShard(String shardName) { |
| checkState(isComponentShard(), "Only the componentShard can create other shards."); |
| return new ShardImplementation(name.nestedClass(shardName)); |
| } |
| |
| /** Returns the {@link ComponentImplementation} that owns this shard. */ |
| public ComponentImplementation getComponentImplementation() { |
| return ComponentImplementation.this; |
| } |
| |
| /** |
| * Returns {@code true} if this shard represents the component implementation rather than a |
| * separate {@code Shard} class. |
| */ |
| public boolean isComponentShard() { |
| return this == componentShard; |
| } |
| |
| /** Returns the fields for all components in the component path by component implementation. */ |
| public ImmutableMap<ComponentImplementation, FieldSpec> componentFieldsByImplementation() { |
| return componentFieldsByImplementation; |
| } |
| |
| /** Returns a reference to this implementation when called from a different class. */ |
| public CodeBlock shardFieldReference() { |
| if (!isComponentShard() && !shardFieldsByImplementation.containsKey(this)) { |
| // Add the shard if this is the first time it's requested by something. |
| String shardFieldName = |
| componentShard.getUniqueFieldName(UPPER_CAMEL.to(LOWER_CAMEL, name.simpleName())); |
| FieldSpec shardField = FieldSpec.builder(name, shardFieldName, PRIVATE).build(); |
| |
| shardFieldsByImplementation.put(this, shardField); |
| } |
| // TODO(bcorso): This currently relies on all requesting classes having a reference to the |
| // component with the same name, which is kind of sketchy. Try to think of a better way that |
| // can accomodate the component missing in some classes if it's not used. |
| return isComponentShard() |
| ? componentFieldReference() |
| : CodeBlock.of("$L.$N", componentFieldReference(), shardFieldsByImplementation.get(this)); |
| } |
| |
| // TODO(ronshapiro): see if we can remove this method and instead inject it in the objects that |
| // need it. |
| /** Returns the binding graph for the component being generated. */ |
| public BindingGraph graph() { |
| return graph; |
| } |
| |
| /** Returns the descriptor for the component being generated. */ |
| public ComponentDescriptor componentDescriptor() { |
| return graph.componentDescriptor(); |
| } |
| |
| /** Returns the name of the component. */ |
| public ClassName name() { |
| return name; |
| } |
| |
| /** |
| * Returns the name of the creator implementation class for the given subcomponent creator |
| * {@link Key}. |
| */ |
| ClassName getSubcomponentCreatorSimpleName(Key creatorKey) { |
| return componentNames.getSubcomponentCreatorName(graph.componentPath(), creatorKey); |
| } |
| |
| /** |
| * Returns an accessible type for this shard implementation, returns Object if the type is not |
| * accessible. |
| * |
| * <p>This method checks accessibility for public types and package private types, and it also |
| * checks protected types' accessibility. |
| */ |
| TypeMirror accessibleType(TypeMirror type) { |
| // Returns the original type if the type is accessible from this shard, or returns original |
| // type's raw type if only its raw type is accessible. Otherwise, returns Object. |
| TypeMirror castedType = types.accessibleType(type, name()); |
| // Previous check marks protected type as inaccessible, so a second check is needed to check |
| // if the type is protected type and accessible. |
| if (TypeName.get(castedType).equals(TypeName.OBJECT) && isTypeAccessible(type)) { |
| castedType = type; |
| } |
| return castedType; |
| } |
| |
| /** |
| * Returns {@code true} if {@code type} is accessible from the generated component. |
| * |
| * <p>This method checks accessibility for public types and package private types, and it also |
| * checks protected types' accessibility. |
| */ |
| boolean isTypeAccessible(TypeMirror type) { |
| if (isTypeAccessibleFrom(type, name.packageName())) { |
| return true; |
| } |
| // Check if the type is protected and accessible from current component. |
| if (type instanceof DeclaredType |
| && isProtectedMemberOf( |
| MoreTypes.asDeclared(type), |
| getComponentImplementation().componentDescriptor().typeElement())) { |
| return true; |
| } |
| return false; |
| } |
| |
| // TODO(dpb): Consider taking FieldSpec, and returning identical FieldSpec with unique name? |
| /** Adds the given field to the component. */ |
| public void addField(FieldSpecKind fieldKind, FieldSpec fieldSpec) { |
| fieldSpecsMap.put(fieldKind, fieldSpec); |
| } |
| |
| // TODO(dpb): Consider taking MethodSpec, and returning identical MethodSpec with unique name? |
| /** Adds the given method to the component. */ |
| public void addMethod(MethodSpecKind methodKind, MethodSpec methodSpec) { |
| methodSpecsMap.put(methodKind, methodSpec); |
| } |
| |
| /** Adds the given type to the component. */ |
| public void addType(TypeSpecKind typeKind, TypeSpec typeSpec) { |
| typeSpecsMap.put(typeKind, typeSpec); |
| } |
| |
| /** Adds a {@link Supplier} for the SwitchingProvider for the component. */ |
| void addTypeSupplier(Supplier<TypeSpec> typeSpecSupplier) { |
| typeSuppliers.add(typeSpecSupplier); |
| } |
| |
| /** Adds the given code block to the initialize methods of the component. */ |
| void addInitialization(CodeBlock codeBlock) { |
| initializations.add(codeBlock); |
| } |
| |
| /** Adds the given code block that initializes a {@link ComponentRequirement}. */ |
| void addComponentRequirementInitialization(CodeBlock codeBlock) { |
| componentRequirementInitializations.add(codeBlock); |
| } |
| |
| /** |
| * Adds the given cancellation statement to the cancellation listener method of the component. |
| */ |
| void addCancellation(Key key, CodeBlock codeBlock) { |
| // Store cancellations by key to avoid adding the same cancellation twice. |
| cancellations.putIfAbsent(key, codeBlock); |
| } |
| |
| /** Returns a new, unique field name for the component based on the given name. */ |
| String getUniqueFieldName(String name) { |
| return componentFieldNames.getUniqueName(name); |
| } |
| |
| String getUniqueAssistedParamName(String name) { |
| if (!initialized) { |
| // Assisted params will be used in switching provider, so they can't conflict with component |
| // field names in switching provider. {@link UniqueNameSet#getUniqueName} will add the |
| // component field names to the unique set if it does not exists. If the name already exists |
| // in the set, then a dedupe will be performed automatically on the passed in name, and the |
| // newly created unique name will then be added to the set. |
| componentFieldsByImplementation() |
| .values() |
| .forEach(fieldSpec -> assistedParamNames.getUniqueName(fieldSpec.name)); |
| initialized = true; |
| } |
| return assistedParamNames.getUniqueName(name); |
| } |
| |
| public String getUniqueFieldNameForAssistedParam(VariableElement element) { |
| if (uniqueAssistedName.containsKey(element)) { |
| return uniqueAssistedName.get(element); |
| } |
| String name = getUniqueAssistedParamName(element.getSimpleName().toString()); |
| uniqueAssistedName.put(element, name); |
| return name; |
| } |
| |
| /** Returns a new, unique nested class name for the component based on the given name. */ |
| public String getUniqueMethodName(String name) { |
| return componentMethodNames.getUniqueName(name); |
| } |
| |
| /** Returns a new, unique method name for a getter method for the given request. */ |
| String getUniqueMethodName(BindingRequest request) { |
| return uniqueMethodName(request, KeyVariableNamer.name(request.key())); |
| } |
| |
| /** Returns a new, unique method name for the component based on the given name. */ |
| public String getUniqueClassName(String name) { |
| return componentClassNames.getUniqueName(name); |
| } |
| |
| private String uniqueMethodName(BindingRequest request, String bindingName) { |
| // This name is intentionally made to match the name for fields in fastInit |
| // in order to reduce the constant pool size. b/162004246 |
| String baseMethodName = |
| bindingName |
| + (request.isRequestKind(RequestKind.INSTANCE) |
| ? "" |
| : UPPER_UNDERSCORE.to(UPPER_CAMEL, request.kindName())); |
| return getUniqueMethodName(baseMethodName); |
| } |
| |
| /** |
| * Gets the parameter name to use for the given requirement for this component, starting with |
| * the given base name if no parameter name has already been selected for the requirement. |
| */ |
| public String getParameterName(ComponentRequirement requirement) { |
| return constructorParameters.get(requirement).name; |
| } |
| |
| /** Claims a new method name for the component. Does nothing if method name already exists. */ |
| public void claimMethodName(CharSequence name) { |
| componentMethodNames.claim(name); |
| } |
| |
| /** Generates the component and returns the resulting {@link TypeSpec.Builder}. */ |
| private TypeSpec generate() { |
| TypeSpec.Builder builder = classBuilder(name); |
| |
| if (isComponentShard()) { |
| TypeSpecs.addSupertype(builder, graph.componentTypeElement()); |
| addCreator(); |
| addFactoryMethods(); |
| addInterfaceMethods(); |
| addChildComponents(); |
| addShards(); |
| } |
| |
| addConstructorAndInitializationMethods(); |
| |
| if (graph.componentDescriptor().isProduction()) { |
| if (isComponentShard() || !cancellations.isEmpty()) { |
| TypeSpecs.addSupertype( |
| builder, elements.getTypeElement(TypeNames.CANCELLATION_LISTENER.canonicalName())); |
| addCancellationListenerImplementation(); |
| } |
| } |
| |
| modifiers().forEach(builder::addModifiers); |
| fieldSpecsMap.asMap().values().forEach(builder::addFields); |
| methodSpecsMap.asMap().values().forEach(builder::addMethods); |
| typeSpecsMap.asMap().values().forEach(builder::addTypes); |
| typeSuppliers.stream().map(Supplier::get).forEach(builder::addType); |
| return builder.build(); |
| } |
| |
| private ImmutableSet<Modifier> modifiers() { |
| if (!isComponentShard()) { |
| // TODO(bcorso): Consider making shards static and unnested too? |
| return ImmutableSet.of(PRIVATE, FINAL); |
| } else if (isNested()) { |
| return ImmutableSet.of(PRIVATE, STATIC, FINAL); |
| } |
| return graph.componentTypeElement().isPublic() |
| // TODO(ronshapiro): perhaps all generated components should be non-public? |
| ? ImmutableSet.of(PUBLIC, FINAL) |
| : ImmutableSet.of(FINAL); |
| } |
| |
| private void addCreator() { |
| componentCreatorImplementationFactoryProvider |
| .get() |
| .create() |
| .map(ComponentCreatorImplementation::spec) |
| .ifPresent( |
| creator -> |
| rootComponentImplementation() |
| .getComponentShard() |
| .addType(TypeSpecKind.COMPONENT_CREATOR, creator)); |
| } |
| |
| private void addFactoryMethods() { |
| if (parent.isPresent()) { |
| graph |
| .factoryMethod() |
| .map(XConverters::toJavac) |
| .ifPresent(this::createSubcomponentFactoryMethod); |
| } else { |
| createRootComponentFactoryMethod(); |
| } |
| } |
| |
| private void createRootComponentFactoryMethod() { |
| checkState(!parent.isPresent()); |
| // Top-level components have a static method that returns a builder or factory for the |
| // component. If the user defined a @Component.Builder or @Component.Factory, an |
| // implementation of their type is returned. Otherwise, an autogenerated Builder type is |
| // returned. |
| // TODO(cgdecker): Replace this abomination with a small class? |
| // Better yet, change things so that an autogenerated builder type has a descriptor of sorts |
| // just like a user-defined creator type. |
| ComponentCreatorKind creatorKind; |
| ClassName creatorType; |
| String factoryMethodName; |
| boolean noArgFactoryMethod; |
| Optional<ComponentCreatorDescriptor> creatorDescriptor = |
| graph.componentDescriptor().creatorDescriptor(); |
| if (creatorDescriptor.isPresent()) { |
| ComponentCreatorDescriptor descriptor = creatorDescriptor.get(); |
| creatorKind = descriptor.kind(); |
| creatorType = descriptor.typeElement().getClassName(); |
| factoryMethodName = getSimpleName(descriptor.factoryMethod()); |
| noArgFactoryMethod = descriptor.factoryParameters().isEmpty(); |
| } else { |
| creatorKind = BUILDER; |
| creatorType = getCreatorName(); |
| factoryMethodName = "build"; |
| noArgFactoryMethod = true; |
| } |
| validateMethodNameDoesNotOverrideGeneratedCreator(creatorKind.methodName()); |
| claimMethodName(creatorKind.methodName()); |
| MethodSpec creatorFactoryMethod = |
| methodBuilder(creatorKind.methodName()) |
| .addModifiers(PUBLIC, STATIC) |
| .returns(creatorType) |
| .addStatement("return new $T()", getCreatorName()) |
| .build(); |
| addMethod(MethodSpecKind.BUILDER_METHOD, creatorFactoryMethod); |
| if (noArgFactoryMethod && canInstantiateAllRequirements()) { |
| validateMethodNameDoesNotOverrideGeneratedCreator("create"); |
| claimMethodName("create"); |
| addMethod( |
| MethodSpecKind.BUILDER_METHOD, |
| methodBuilder("create") |
| .returns(graph.componentTypeElement().getClassName()) |
| .addModifiers(PUBLIC, STATIC) |
| .addStatement("return new $L().$L()", creatorKind.typeName(), factoryMethodName) |
| .build()); |
| } |
| } |
| |
| private void validateMethodNameDoesNotOverrideGeneratedCreator(String creatorName) { |
| // Check if there is any client added method has the same signature as generated creatorName. |
| MoreElements.getAllMethods(toJavac(graph.componentTypeElement()), types, elements).stream() |
| .filter(method -> method.getSimpleName().contentEquals(creatorName)) |
| .filter(method -> method.getParameters().isEmpty()) |
| .filter(method -> !method.getModifiers().contains(Modifier.STATIC)) |
| .forEach( |
| (ExecutableElement method) -> |
| messager.printMessage( |
| ERROR, |
| String.format( |
| "Cannot override generated method: %s.%s()", |
| method.getEnclosingElement().getSimpleName(), method.getSimpleName()))); |
| } |
| |
| /** {@code true} if all of the graph's required dependencies can be automatically constructed */ |
| private boolean canInstantiateAllRequirements() { |
| return !Iterables.any( |
| graph.componentRequirements(), ComponentRequirement::requiresAPassedInstance); |
| } |
| |
| private void createSubcomponentFactoryMethod(ExecutableElement factoryMethod) { |
| checkState(parent.isPresent()); |
| Collection<ParameterSpec> params = |
| Maps.transformValues( |
| graph.factoryMethodParameters(), |
| parameter -> ParameterSpec.get(toJavac(parameter))) |
| .values(); |
| DeclaredType parentType = |
| asDeclared(toJavac(parent.get().graph().componentTypeElement()).asType()); |
| MethodSpec.Builder method = MethodSpec.overriding(factoryMethod, parentType, types); |
| params.forEach( |
| param -> method.addStatement("$T.checkNotNull($N)", Preconditions.class, param)); |
| method.addStatement( |
| "return new $T($L)", |
| name(), |
| parameterNames( |
| ImmutableList.<ParameterSpec>builder() |
| .addAll( |
| creatorComponentFields().stream() |
| .map(field -> ParameterSpec.builder(field.type, field.name).build()) |
| .collect(toImmutableList())) |
| .addAll(params) |
| .build())); |
| |
| parent.get().getComponentShard().addMethod(COMPONENT_METHOD, method.build()); |
| } |
| |
| private void addInterfaceMethods() { |
| // Each component method may have been declared by several supertypes. We want to implement |
| // only one method for each distinct signature. |
| XType componentType = graph.componentTypeElement().getType(); |
| Set<MethodSignature> signatures = Sets.newHashSet(); |
| for (ComponentMethodDescriptor method : graph.componentDescriptor().entryPointMethods()) { |
| if (signatures.add(MethodSignature.forComponentMethod(method, componentType))) { |
| addMethod(COMPONENT_METHOD, bindingExpressionsProvider.get().getComponentMethod(method)); |
| } |
| } |
| } |
| |
| private void addChildComponents() { |
| for (BindingGraph subgraph : graph.subgraphs()) { |
| rootComponentImplementation() |
| .getComponentShard() |
| .addType( |
| TypeSpecKind.SUBCOMPONENT, |
| childComponentImplementationFactory.create(subgraph).generate()); |
| } |
| } |
| |
| private void addShards() { |
| // Generate all shards and add them to this component implementation. |
| for (ShardImplementation shard : ImmutableSet.copyOf(shardsByBinding.values())) { |
| if (shardFieldsByImplementation.containsKey(shard)) { |
| addField(FieldSpecKind.COMPONENT_SHARD_FIELD, shardFieldsByImplementation.get(shard)); |
| TypeSpec shardTypeSpec = shard.generate(); |
| addType(TypeSpecKind.COMPONENT_SHARD_TYPE, shardTypeSpec); |
| } |
| } |
| } |
| |
| /** Creates and adds the constructor and methods needed for initializing the component. */ |
| private void addConstructorAndInitializationMethods() { |
| MethodSpec.Builder constructor = constructorBuilder().addModifiers(PRIVATE); |
| ImmutableList<ParameterSpec> parameters = constructorParameters.values().asList(); |
| |
| if (isComponentShard()) { |
| // Add a constructor parameter and initialization for each component field. We initialize |
| // these fields immediately so that we don't need to be pass them to each initialize method |
| // and shard constructor. |
| componentFieldsByImplementation() |
| .forEach( |
| (componentImplementation, field) -> { |
| if (componentImplementation.equals(ComponentImplementation.this)) { |
| // For the self-referenced component field, |
| // just initialize it in the initializer. |
| addField( |
| FieldSpecKind.COMPONENT_REQUIREMENT_FIELD, |
| field.toBuilder().initializer("this").build()); |
| } else { |
| addField(FieldSpecKind.COMPONENT_REQUIREMENT_FIELD, field); |
| constructor.addStatement("this.$1N = $1N", field); |
| constructor.addParameter(field.type, field.name); |
| } |
| }); |
| constructor.addCode(CodeBlocks.concat(componentRequirementInitializations)); |
| } |
| constructor.addParameters(parameters); |
| |
| // TODO(cgdecker): It's not the case that each initialize() method has need for all of the |
| // given parameters. In some cases, those parameters may have already been assigned to fields |
| // which could be referenced instead. In other cases, an initialize method may just not need |
| // some of the parameters because the set of initializations in that partition does not |
| // include any reference to them. Right now, the Dagger code has no way of getting that |
| // information because, among other things, componentImplementation.getImplementations() just |
| // returns a bunch of CodeBlocks with no semantic information. Additionally, we may not know |
| // yet whether a field will end up needing to be created for a specific requirement, and we |
| // don't want to create a field that ends up only being used during initialization. |
| CodeBlock args = parameterNames(parameters); |
| ImmutableList<MethodSpec> initializationMethods = |
| createPartitionedMethods( |
| "initialize", |
| // TODO(bcorso): Rather than passing in all of the constructor parameters, keep track |
| // of which parameters are used during initialization and only pass those. This could |
| // be useful for FastInit, where most of the initializations are just calling |
| // SwitchingProvider with no parameters. |
| makeFinal(parameters), |
| initializations, |
| methodName -> |
| methodBuilder(methodName) |
| /* TODO(gak): Strictly speaking, we only need the suppression here if we are |
| * also initializing a raw field in this method, but the structure of this |
| * code makes it awkward to pass that bit through. This will be cleaned up |
| * when we no longer separate fields and initialization as we do now. */ |
| .addAnnotation(suppressWarnings(UNCHECKED))); |
| |
| for (MethodSpec initializationMethod : initializationMethods) { |
| constructor.addStatement("$N($L)", initializationMethod, args); |
| addMethod(MethodSpecKind.INITIALIZE_METHOD, initializationMethod); |
| } |
| |
| if (isComponentShard()) { |
| constructor.addCode(CodeBlocks.concat(shardInitializations)); |
| } else { |
| // This initialization is called from the componentShard, so we need to use those args. |
| CodeBlock componentArgs = |
| parameterNames(componentShard.constructorParameters.values().asList()); |
| FieldSpec shardField = shardFieldsByImplementation.get(this); |
| shardInitializations.add(CodeBlock.of("$N = new $T($L);", shardField, name, componentArgs)); |
| } |
| |
| addMethod(MethodSpecKind.CONSTRUCTOR, constructor.build()); |
| } |
| |
| private void addCancellationListenerImplementation() { |
| MethodSpec.Builder methodBuilder = |
| methodBuilder(CANCELLATION_LISTENER_METHOD_NAME) |
| .addModifiers(PUBLIC) |
| .addAnnotation(Override.class) |
| .addParameter(MAY_INTERRUPT_IF_RUNNING_PARAM); |
| |
| // Reversing should order cancellations starting from entry points and going down to leaves |
| // rather than the other way around. This shouldn't really matter but seems *slightly* |
| // preferable because: |
| // When a future that another future depends on is cancelled, that cancellation will propagate |
| // up the future graph toward the entry point. Cancelling in reverse order should ensure that |
| // everything that depends on a particular node has already been cancelled when that node is |
| // cancelled, so there's no need to propagate. Otherwise, when we cancel a leaf node, it might |
| // propagate through most of the graph, making most of the cancel calls that follow in the |
| // onProducerFutureCancelled method do nothing. |
| if (isComponentShard()) { |
| methodBuilder.addCode( |
| CodeBlocks.concat(ImmutableList.copyOf(shardCancellations).reverse())); |
| } else if (!cancellations.isEmpty()) { |
| shardCancellations.add( |
| CodeBlock.of( |
| "$N.$N($N);", |
| shardFieldsByImplementation.get(this), |
| CANCELLATION_LISTENER_METHOD_NAME, |
| MAY_INTERRUPT_IF_RUNNING_PARAM)); |
| } |
| |
| ImmutableList<CodeBlock> cancellationStatements = |
| ImmutableList.copyOf(cancellations.values()).reverse(); |
| if (cancellationStatements.size() < STATEMENTS_PER_METHOD) { |
| methodBuilder.addCode(CodeBlocks.concat(cancellationStatements)).build(); |
| } else { |
| ImmutableList<MethodSpec> cancelProducersMethods = |
| createPartitionedMethods( |
| "cancelProducers", |
| ImmutableList.of(MAY_INTERRUPT_IF_RUNNING_PARAM), |
| cancellationStatements, |
| methodName -> methodBuilder(methodName).addModifiers(PRIVATE)); |
| for (MethodSpec cancelProducersMethod : cancelProducersMethods) { |
| methodBuilder.addStatement( |
| "$N($N)", cancelProducersMethod, MAY_INTERRUPT_IF_RUNNING_PARAM); |
| addMethod(MethodSpecKind.CANCELLATION_LISTENER_METHOD, cancelProducersMethod); |
| } |
| } |
| |
| if (isComponentShard()) { |
| cancelParentStatement().ifPresent(methodBuilder::addCode); |
| } |
| |
| addMethod(MethodSpecKind.CANCELLATION_LISTENER_METHOD, methodBuilder.build()); |
| } |
| |
| private Optional<CodeBlock> cancelParentStatement() { |
| if (!shouldPropagateCancellationToParent()) { |
| return Optional.empty(); |
| } |
| return Optional.of( |
| CodeBlock.builder() |
| .addStatement( |
| "$L.$N($N)", |
| parent.get().componentFieldReference(), |
| CANCELLATION_LISTENER_METHOD_NAME, |
| MAY_INTERRUPT_IF_RUNNING_PARAM) |
| .build()); |
| } |
| |
| private boolean shouldPropagateCancellationToParent() { |
| return parent.isPresent() |
| && parent |
| .get() |
| .componentDescriptor() |
| .cancellationPolicy() |
| .map(policy -> policy.fromSubcomponents().equals(PROPAGATE)) |
| .orElse(false); |
| } |
| |
| /** |
| * Creates one or more methods, all taking the given {@code parameters}, which partition the |
| * given list of {@code statements} among themselves such that no method has more than {@code |
| * STATEMENTS_PER_METHOD} statements in it and such that the returned methods, if called in |
| * order, will execute the {@code statements} in the given order. |
| */ |
| private ImmutableList<MethodSpec> createPartitionedMethods( |
| String methodName, |
| Iterable<ParameterSpec> parameters, |
| List<CodeBlock> statements, |
| Function<String, MethodSpec.Builder> methodBuilderCreator) { |
| return Lists.partition(statements, STATEMENTS_PER_METHOD).stream() |
| .map( |
| partition -> |
| methodBuilderCreator |
| .apply(getUniqueMethodName(methodName)) |
| .addModifiers(PRIVATE) |
| .addParameters(parameters) |
| .addCode(CodeBlocks.concat(partition)) |
| .build()) |
| .collect(toImmutableList()); |
| } |
| } |
| |
| private static ImmutableList<ComponentRequirement> constructorRequirements(BindingGraph graph) { |
| if (graph.componentDescriptor().hasCreator()) { |
| return graph.componentRequirements().asList(); |
| } else if (graph.factoryMethod().isPresent()) { |
| return graph.factoryMethodParameters().keySet().asList(); |
| } else { |
| throw new AssertionError( |
| "Expected either a component creator or factory method but found neither."); |
| } |
| } |
| |
| private static ImmutableList<ParameterSpec> makeFinal(List<ParameterSpec> parameters) { |
| return parameters.stream() |
| .map(param -> param.toBuilder().addModifiers(FINAL).build()) |
| .collect(toImmutableList()); |
| } |
| } |