| /* |
| * Copyright (C) 2017 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.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Suppliers.memoize; |
| import static dagger.internal.codegen.writing.ComponentImplementation.FieldSpecKind.COMPONENT_REQUIREMENT_FIELD; |
| import static javax.lang.model.element.Modifier.FINAL; |
| import static javax.lang.model.element.Modifier.PRIVATE; |
| |
| import com.google.common.base.Supplier; |
| import com.squareup.javapoet.ClassName; |
| import com.squareup.javapoet.CodeBlock; |
| import com.squareup.javapoet.FieldSpec; |
| import com.squareup.javapoet.TypeName; |
| import dagger.internal.codegen.binding.BindingGraph; |
| import dagger.internal.codegen.binding.ComponentRequirement; |
| import dagger.internal.codegen.langmodel.DaggerElements; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Optional; |
| import javax.inject.Inject; |
| import javax.lang.model.element.TypeElement; |
| |
| /** |
| * A central repository of expressions used to access any {@link ComponentRequirement} available to |
| * a component. |
| */ |
| @PerComponentImplementation |
| public final class ComponentRequirementExpressions { |
| |
| // TODO(dpb,ronshapiro): refactor this and ComponentBindingExpressions into a |
| // HierarchicalComponentMap<K, V>, or perhaps this use a flattened ImmutableMap, built from its |
| // parents? If so, maybe make ComponentRequirementExpression.Factory create it. |
| |
| private final Optional<ComponentRequirementExpressions> parent; |
| private final Map<ComponentRequirement, ComponentRequirementExpression> |
| componentRequirementExpressions = new HashMap<>(); |
| private final BindingGraph graph; |
| private final ComponentImplementation componentImplementation; |
| private final ModuleProxies moduleProxies; |
| |
| // TODO(ronshapiro): give ComponentImplementation a graph() method |
| @Inject |
| ComponentRequirementExpressions( |
| @ParentComponent Optional<ComponentRequirementExpressions> parent, |
| BindingGraph graph, |
| ComponentImplementation componentImplementation, |
| DaggerElements elements, |
| ModuleProxies moduleProxies) { |
| this.parent = parent; |
| this.graph = graph; |
| this.componentImplementation = componentImplementation; |
| this.moduleProxies = moduleProxies; |
| } |
| |
| /** |
| * Returns an expression for the {@code componentRequirement} to be used when implementing a |
| * component method. This may add a field or method to the component in order to reference the |
| * component requirement outside of the {@code initialize()} methods. |
| */ |
| CodeBlock getExpression(ComponentRequirement componentRequirement, ClassName requestingClass) { |
| return getExpression(componentRequirement).getExpression(requestingClass); |
| } |
| |
| /** |
| * Returns an expression for the {@code componentRequirement} to be used only within {@code |
| * initialize()} methods, where the component constructor parameters are available. |
| * |
| * <p>When accessing this expression from a subcomponent, this may cause a field to be initialized |
| * or a method to be added in the component that owns this {@link ComponentRequirement}. |
| */ |
| CodeBlock getExpressionDuringInitialization( |
| ComponentRequirement componentRequirement, ClassName requestingClass) { |
| return getExpression(componentRequirement).getExpressionDuringInitialization(requestingClass); |
| } |
| |
| ComponentRequirementExpression getExpression(ComponentRequirement componentRequirement) { |
| if (graph.componentRequirements().contains(componentRequirement)) { |
| return componentRequirementExpressions.computeIfAbsent( |
| componentRequirement, this::createField); |
| } |
| if (parent.isPresent()) { |
| return parent.get().getExpression(componentRequirement); |
| } |
| |
| throw new IllegalStateException( |
| "no component requirement expression found for " + componentRequirement); |
| } |
| |
| /** Returns a field for a {@link ComponentRequirement}. */ |
| private ComponentRequirementExpression createField(ComponentRequirement requirement) { |
| if (componentImplementation.componentDescriptor().hasCreator()) { |
| return new ComponentParameterField(requirement, componentImplementation, Optional.empty()); |
| } else if (graph.factoryMethod().isPresent() |
| && graph.factoryMethodParameters().containsKey(requirement)) { |
| String parameterName = |
| graph.factoryMethodParameters().get(requirement).getSimpleName().toString(); |
| return new ComponentParameterField( |
| requirement, componentImplementation, Optional.of(parameterName)); |
| } else if (requirement.kind().isModule()) { |
| return new InstantiableModuleField(requirement, componentImplementation); |
| } else { |
| throw new AssertionError( |
| String.format("Can't create %s in %s", requirement, componentImplementation.name())); |
| } |
| } |
| |
| private abstract static class AbstractField implements ComponentRequirementExpression { |
| final ComponentRequirement componentRequirement; |
| final ComponentImplementation componentImplementation; |
| final String fieldName; |
| private final Supplier<MemberSelect> field = memoize(this::addField); |
| |
| private AbstractField( |
| ComponentRequirement componentRequirement, |
| ComponentImplementation componentImplementation) { |
| this.componentRequirement = checkNotNull(componentRequirement); |
| this.componentImplementation = checkNotNull(componentImplementation); |
| // Note: The field name is being claimed eagerly here even though we don't know at this point |
| // whether or not the requirement will even need a field. This is done because: |
| // A) ComponentParameterField wants to ensure that it doesn't give the parameter the same name |
| // as any field in the component, which requires that it claim a "field name" for itself |
| // when naming the parameter. |
| // B) The parameter name may be needed before the field name is. |
| // C) We want to prefer giving the best name to the field rather than the parameter given its |
| // wider scope. |
| this.fieldName = |
| componentImplementation.getUniqueFieldName(componentRequirement.variableName()); |
| } |
| |
| @Override |
| public CodeBlock getExpression(ClassName requestingClass) { |
| return field.get().getExpressionFor(requestingClass); |
| } |
| |
| private MemberSelect addField() { |
| FieldSpec field = createField(); |
| componentImplementation.addField(COMPONENT_REQUIREMENT_FIELD, field); |
| componentImplementation.addComponentRequirementInitialization(fieldInitialization(field)); |
| return MemberSelect.localField(componentImplementation.name(), fieldName); |
| } |
| |
| private FieldSpec createField() { |
| return FieldSpec.builder(TypeName.get(componentRequirement.type()), fieldName, PRIVATE, FINAL) |
| .build(); |
| } |
| |
| /** Returns the {@link CodeBlock} that initializes the component field during construction. */ |
| abstract CodeBlock fieldInitialization(FieldSpec componentField); |
| } |
| |
| /** |
| * A {@link ComponentRequirementExpression} for {@link ComponentRequirement}s that can be |
| * instantiated by the component (i.e. a static class with a no-arg constructor). |
| */ |
| private final class InstantiableModuleField extends AbstractField { |
| private final TypeElement moduleElement; |
| |
| private InstantiableModuleField( |
| ComponentRequirement module, ComponentImplementation componentImplementation) { |
| super(module, componentImplementation); |
| checkArgument(module.kind().isModule()); |
| this.moduleElement = module.typeElement(); |
| } |
| |
| @Override |
| CodeBlock fieldInitialization(FieldSpec componentField) { |
| return CodeBlock.of( |
| "this.$N = $L;", |
| componentField, |
| moduleProxies.newModuleInstance(moduleElement, componentImplementation.name())); |
| } |
| } |
| |
| /** |
| * A {@link ComponentRequirementExpression} for {@link ComponentRequirement}s that are passed in |
| * as parameters to the component's constructor. |
| */ |
| private static final class ComponentParameterField extends AbstractField { |
| private final String parameterName; |
| |
| private ComponentParameterField( |
| ComponentRequirement componentRequirement, |
| ComponentImplementation componentImplementation, |
| Optional<String> name) { |
| super(componentRequirement, componentImplementation); |
| // Get the name that the component implementation will use for its parameter for the |
| // requirement. If the given name is different than the name of the field created for the |
| // requirement (as may be the case when the parameter name is derived from a user-written |
| // factory method parameter), just use that as the base name for the parameter. Otherwise, |
| // append "Param" to the end of the name to differentiate. |
| // In either case, componentImplementation.getParameterName() will ensure that the final name |
| // that is used is not the same name as any field in the component even if there's something |
| // weird where the component actually has fields named, say, "foo" and "fooParam". |
| String baseName = name.filter(n -> !n.equals(fieldName)).orElse(fieldName + "Param"); |
| this.parameterName = componentImplementation.getParameterName(componentRequirement, baseName); |
| } |
| |
| @Override |
| public CodeBlock getExpressionDuringInitialization(ClassName requestingClass) { |
| if (componentImplementation.name().equals(requestingClass)) { |
| return CodeBlock.of("$L", parameterName); |
| } else { |
| // requesting this component requirement during initialization of a child component requires |
| // it to be accessed from a field and not the parameter (since it is no longer available) |
| return getExpression(requestingClass); |
| } |
| } |
| |
| @Override |
| CodeBlock fieldInitialization(FieldSpec componentField) { |
| // Don't checkNotNull here because the parameter may be nullable; if it isn't, the caller |
| // should handle checking that before passing the parameter. |
| return CodeBlock.of("this.$N = $L;", componentField, parameterName); |
| } |
| } |
| } |