/* * 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 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.TypeSpec.classBuilder; import static dagger.internal.codegen.binding.ComponentCreatorKind.BUILDER; import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; 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 com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import dagger.internal.codegen.base.UniqueNameSet; import dagger.internal.codegen.binding.BindingGraph; 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.ComponentRequirement; import dagger.internal.codegen.binding.KeyVariableNamer; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.javapoet.TypeSpecs; import dagger.model.Key; import dagger.model.RequestKind; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; /** The implementation of a component type. */ public final class ComponentImplementation { /** A type of field that this component can contain. */ public enum FieldSpecKind { /** A field for a component shard. */ COMPONENT_SHARD, /** A field required by the component, e.g. module instances. */ COMPONENT_REQUIREMENT_FIELD, /** * A field for the lock and cached value for {@linkplain PrivateMethodBindingExpression * private-method scoped bindings}. */ PRIVATE_METHOD_SCOPED_FIELD, /** A framework field for type T, e.g. {@code Provider}. */ 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 the subcomponent or subcomponent builder. */ SUBCOMPONENT } private ComponentImplementation currentShard = this; private final Map shardsByKey = new HashMap<>(); private final Optional shardOwner; private final BindingGraph graph; private final ClassName name; private final TypeSpec.Builder component; private final SubcomponentNames subcomponentNames; private final CompilerOptions compilerOptions; private final CodeBlock externalReferenceBlock; private final UniqueNameSet componentFieldNames = new UniqueNameSet(); private final UniqueNameSet componentMethodNames = new UniqueNameSet(); private final List initializations = new ArrayList<>(); private final List componentRequirementInitializations = new ArrayList<>(); private final Map componentRequirementParameterNames = new HashMap<>(); private final Set cancellableProducerKeys = new LinkedHashSet<>(); private final ListMultimap fieldSpecsMap = MultimapBuilder.enumKeys(FieldSpecKind.class).arrayListValues().build(); private final ListMultimap methodSpecsMap = MultimapBuilder.enumKeys(MethodSpecKind.class).arrayListValues().build(); private final ListMultimap typeSpecsMap = MultimapBuilder.enumKeys(TypeSpecKind.class).arrayListValues().build(); private final List> typeSuppliers = new ArrayList<>(); private ComponentImplementation( BindingGraph graph, ClassName name, SubcomponentNames subcomponentNames, CompilerOptions compilerOptions) { this.graph = graph; this.name = name; this.component = classBuilder(name); this.subcomponentNames = subcomponentNames; this.shardOwner = Optional.empty(); this.externalReferenceBlock = CodeBlock.of("$T.this", name); this.compilerOptions = compilerOptions; } private ComponentImplementation(ComponentImplementation shardOwner, ClassName shardName) { this.graph = shardOwner.graph; this.name = shardName; this.component = classBuilder(shardName); this.subcomponentNames = shardOwner.subcomponentNames; this.compilerOptions = shardOwner.compilerOptions; this.shardOwner = Optional.of(shardOwner); String fieldName = UPPER_CAMEL.to(LOWER_CAMEL, name.simpleName()); String uniqueFieldName = shardOwner.getUniqueFieldName(fieldName); this.externalReferenceBlock = CodeBlock.of("$T.this.$N", shardOwner.name, uniqueFieldName); shardOwner.addTypeSupplier(() -> generate().build()); shardOwner.addField( FieldSpecKind.COMPONENT_SHARD, FieldSpec.builder(name, uniqueFieldName, PRIVATE, FINAL) .initializer("new $T()", name) .build()); } /** Returns a component implementation for a top-level component. */ public static ComponentImplementation topLevelComponentImplementation( BindingGraph graph, ClassName name, SubcomponentNames subcomponentNames, CompilerOptions compilerOptions) { return new ComponentImplementation(graph, name, subcomponentNames, compilerOptions); } /** Returns a component implementation that is a child of the current implementation. */ public ComponentImplementation childComponentImplementation(BindingGraph graph) { checkState(!shardOwner.isPresent(), "Shards cannot create child components."); ClassName childName = getSubcomponentName(graph.componentDescriptor()); return new ComponentImplementation(graph, childName, subcomponentNames, compilerOptions); } /** Returns a component implementation that is a shard of the current implementation. */ public ComponentImplementation shardImplementation(Key key) { checkState(!shardOwner.isPresent(), "Shards cannot create other shards."); if (!shardsByKey.containsKey(key)) { int keysPerShard = compilerOptions.keysPerComponentShard(graph.componentTypeElement()); if (!shardsByKey.isEmpty() && shardsByKey.size() % keysPerShard == 0) { ClassName shardName = name.nestedClass("Shard" + shardsByKey.size() / keysPerShard); currentShard = new ComponentImplementation(this, shardName); } shardsByKey.put(key, currentShard); } return shardsByKey.get(key); } /** Returns a reference to this compenent when called from a class nested in this component. */ public CodeBlock externalReferenceBlock() { return externalReferenceBlock; } // 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 whether or not the implementation is nested within another class. */ public boolean isNested() { return name.enclosingClassName() != null; } /** * Returns the kind of this component's creator. * * @throws IllegalStateException if the component has no creator */ private ComponentCreatorKind creatorKind() { checkState(componentDescriptor().hasCreator()); return componentDescriptor() .creatorDescriptor() .map(ComponentCreatorDescriptor::kind) .orElse(BUILDER); } /** * 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 isNested() ? name.peerClass(subcomponentNames.getCreatorName(componentDescriptor())) : name.nestedClass(creatorKind().typeName()); } /** Returns the name of the nested implementation class for a child component. */ private ClassName getSubcomponentName(ComponentDescriptor childDescriptor) { checkArgument( componentDescriptor().childComponents().contains(childDescriptor), "%s is not a child component of %s", childDescriptor.typeElement(), componentDescriptor().typeElement()); // TODO(erichang): Hacky fix to shorten the suffix if we're too deeply // nested to save on file name length. 2 chosen arbitrarily. String suffix = name.simpleNames().size() > 2 ? "I" : "Impl"; return name.nestedClass(subcomponentNames.get(childDescriptor) + suffix); } /** * Returns the simple name of the creator implementation class for the given subcomponent creator * {@link Key}. */ String getSubcomponentCreatorSimpleName(Key key) { return subcomponentNames.getCreatorName(key); } /** Returns {@code true} if {@code type} is accessible from the generated component. */ boolean isTypeAccessible(TypeMirror type) { return isTypeAccessibleFrom(type, name.packageName()); } /** Adds the given super type to the component. */ public void addSupertype(TypeElement supertype) { TypeSpecs.addSupertype(component, supertype); } // 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 annotation to the component. */ public void addAnnotation(AnnotationSpec annotation) { component.addAnnotation(annotation); } /** 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 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); } /** * Marks the given key of a producer as one that should have a cancellation statement in the * cancellation listener method of the component. */ void addCancellableProducerKey(Key key) { cancellableProducerKeys.add(key); } /** Returns a new, unique field name for the component based on the given name. */ String getUniqueFieldName(String name) { return componentFieldNames.getUniqueName(name); } /** Returns a new, unique method 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())); } 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, String baseName) { return componentRequirementParameterNames.computeIfAbsent( requirement, r -> getUniqueFieldName(baseName)); } /** Claims a new method name for the component. Does nothing if method name already exists. */ public void claimMethodName(CharSequence name) { componentMethodNames.claim(name); } /** Returns the list of {@link CodeBlock}s that need to go in the initialize method. */ public ImmutableList getInitializations() { return ImmutableList.copyOf(initializations); } /** * Returns a list of {@link CodeBlock}s for initializing {@link ComponentRequirement}s. * *

These initializations are kept separate from {@link #getInitializations()} because they must * be executed before the initializations of any framework instance initializations in a * superclass implementation that may depend on the instances. We cannot use the same strategy * that we use for framework instances (i.e. wrap in a {@link dagger.internal.DelegateFactory} or * {@link dagger.producers.internal.DelegateProducer} since the types of these initialized fields * have no interface type that we can write a proxy for. */ // TODO(cgdecker): can these be inlined with getInitializations() now that we've turned down // ahead-of-time subcomponents? public ImmutableList getComponentRequirementInitializations() { return ImmutableList.copyOf(componentRequirementInitializations); } /** * Returns the list of producer {@link Key}s that need cancellation statements in the cancellation * listener method. */ public ImmutableList getCancellableProducerKeys() { return ImmutableList.copyOf(cancellableProducerKeys); } /** Generates the component and returns the resulting {@link TypeSpec.Builder}. */ public TypeSpec.Builder generate() { modifiers().forEach(component::addModifiers); fieldSpecsMap.asMap().values().forEach(component::addFields); methodSpecsMap.asMap().values().forEach(component::addMethods); typeSpecsMap.asMap().values().forEach(component::addTypes); typeSuppliers.stream().map(Supplier::get).forEach(component::addType); return component; } private ImmutableSet modifiers() { if (isNested()) { return ImmutableSet.of(PRIVATE, FINAL); } return graph.componentTypeElement().getModifiers().contains(PUBLIC) // TODO(ronshapiro): perhaps all generated components should be non-public? ? ImmutableSet.of(PUBLIC, FINAL) : ImmutableSet.of(FINAL); } }