1 /* 2 * Copyright (C) 2017 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.internal.codegen.writing; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 import static com.google.common.base.Suppliers.memoize; 22 import static dagger.internal.codegen.writing.ComponentImplementation.FieldSpecKind.COMPONENT_REQUIREMENT_FIELD; 23 import static javax.lang.model.element.Modifier.FINAL; 24 import static javax.lang.model.element.Modifier.PRIVATE; 25 26 import androidx.room.compiler.processing.XTypeElement; 27 import com.google.common.base.Supplier; 28 import com.squareup.javapoet.ClassName; 29 import com.squareup.javapoet.CodeBlock; 30 import com.squareup.javapoet.FieldSpec; 31 import com.squareup.javapoet.TypeName; 32 import dagger.internal.codegen.binding.BindingGraph; 33 import dagger.internal.codegen.binding.ComponentRequirement; 34 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; 35 import java.util.HashMap; 36 import java.util.Map; 37 import java.util.Optional; 38 import javax.inject.Inject; 39 40 /** 41 * A central repository of expressions used to access any {@link ComponentRequirement} available to 42 * a component. 43 */ 44 @PerComponentImplementation 45 public final class ComponentRequirementExpressions { 46 47 // TODO(dpb,ronshapiro): refactor this and ComponentRequestRepresentations into a 48 // HierarchicalComponentMap<K, V>, or perhaps this use a flattened ImmutableMap, built from its 49 // parents? If so, maybe make ComponentRequirementExpression.Factory create it. 50 51 private final Optional<ComponentRequirementExpressions> parent; 52 private final Map<ComponentRequirement, ComponentRequirementExpression> 53 componentRequirementExpressions = new HashMap<>(); 54 private final BindingGraph graph; 55 private final ShardImplementation componentShard; 56 57 @Inject ComponentRequirementExpressions( @arentComponent Optional<ComponentRequirementExpressions> parent, BindingGraph graph, ComponentImplementation componentImplementation)58 ComponentRequirementExpressions( 59 @ParentComponent Optional<ComponentRequirementExpressions> parent, 60 BindingGraph graph, 61 ComponentImplementation componentImplementation) { 62 this.parent = parent; 63 this.graph = graph; 64 // All component requirements go in the componentShard. 65 this.componentShard = componentImplementation.getComponentShard(); 66 } 67 68 /** 69 * Returns an expression for the {@code componentRequirement} to be used when implementing a 70 * component method. This may add a field or method to the component in order to reference the 71 * component requirement outside of the {@code initialize()} methods. 72 */ getExpression(ComponentRequirement componentRequirement, ClassName requestingClass)73 CodeBlock getExpression(ComponentRequirement componentRequirement, ClassName requestingClass) { 74 return getExpression(componentRequirement).getExpression(requestingClass); 75 } 76 getExpression(ComponentRequirement componentRequirement)77 private ComponentRequirementExpression getExpression(ComponentRequirement componentRequirement) { 78 if (graph.componentRequirements().contains(componentRequirement)) { 79 return componentRequirementExpressions.computeIfAbsent( 80 componentRequirement, this::createExpression); 81 } 82 if (parent.isPresent()) { 83 return parent.get().getExpression(componentRequirement); 84 } 85 86 throw new IllegalStateException( 87 "no component requirement expression found for " + componentRequirement); 88 } 89 90 /** 91 * Returns an expression for the {@code componentRequirement} to be used only within {@code 92 * initialize()} methods, where the component constructor parameters are available. 93 * 94 * <p>When accessing this expression from a subcomponent, this may cause a field to be initialized 95 * or a method to be added in the component that owns this {@link ComponentRequirement}. 96 */ getExpressionDuringInitialization( ComponentRequirement componentRequirement, ClassName requestingClass)97 CodeBlock getExpressionDuringInitialization( 98 ComponentRequirement componentRequirement, ClassName requestingClass) { 99 return getExpression(componentRequirement).getExpressionDuringInitialization(requestingClass); 100 } 101 102 /** Returns a field for a {@link ComponentRequirement}. */ createExpression(ComponentRequirement requirement)103 private ComponentRequirementExpression createExpression(ComponentRequirement requirement) { 104 if (componentShard.componentDescriptor().hasCreator() 105 || (graph.factoryMethod().isPresent() 106 && graph.factoryMethodParameters().containsKey(requirement))) { 107 return new ComponentParameterField(requirement); 108 } else if (requirement.kind().isModule()) { 109 return new InstantiableModuleField(requirement); 110 } else { 111 throw new AssertionError( 112 String.format("Can't create %s in %s", requirement, componentShard.name())); 113 } 114 } 115 116 private abstract class AbstractField implements ComponentRequirementExpression { 117 final ComponentRequirement componentRequirement; 118 private final Supplier<MemberSelect> field = memoize(this::createField); 119 AbstractField(ComponentRequirement componentRequirement)120 private AbstractField(ComponentRequirement componentRequirement) { 121 this.componentRequirement = checkNotNull(componentRequirement); 122 } 123 124 @Override getExpression(ClassName requestingClass)125 public CodeBlock getExpression(ClassName requestingClass) { 126 return field.get().getExpressionFor(requestingClass); 127 } 128 createField()129 private MemberSelect createField() { 130 String fieldName = componentShard.getUniqueFieldName(componentRequirement.variableName()); 131 TypeName fieldType = componentRequirement.type().getTypeName(); 132 FieldSpec field = FieldSpec.builder(fieldType, fieldName, PRIVATE, FINAL).build(); 133 componentShard.addField(COMPONENT_REQUIREMENT_FIELD, field); 134 componentShard.addComponentRequirementInitialization(fieldInitialization(field)); 135 return MemberSelect.localField(componentShard, fieldName); 136 } 137 138 /** Returns the {@link CodeBlock} that initializes the component field during construction. */ fieldInitialization(FieldSpec componentField)139 abstract CodeBlock fieldInitialization(FieldSpec componentField); 140 } 141 142 /** 143 * A {@link ComponentRequirementExpression} for {@link ComponentRequirement}s that can be 144 * instantiated by the component (i.e. a static class with a no-arg constructor). 145 */ 146 private final class InstantiableModuleField extends AbstractField { 147 private final XTypeElement moduleElement; 148 InstantiableModuleField(ComponentRequirement module)149 InstantiableModuleField(ComponentRequirement module) { 150 super(module); 151 checkArgument(module.kind().isModule()); 152 this.moduleElement = module.typeElement(); 153 } 154 155 @Override fieldInitialization(FieldSpec componentField)156 CodeBlock fieldInitialization(FieldSpec componentField) { 157 return CodeBlock.of( 158 "this.$N = $L;", 159 componentField, 160 ModuleProxies.newModuleInstance(moduleElement, componentShard.name())); 161 } 162 } 163 164 /** 165 * A {@link ComponentRequirementExpression} for {@link ComponentRequirement}s that are passed in 166 * as parameters to the component's constructor. 167 */ 168 private final class ComponentParameterField extends AbstractField { 169 private final String parameterName; 170 ComponentParameterField(ComponentRequirement module)171 ComponentParameterField(ComponentRequirement module) { 172 super(module); 173 this.parameterName = componentShard.getParameterName(componentRequirement); 174 } 175 176 @Override getExpressionDuringInitialization(ClassName requestingClass)177 public CodeBlock getExpressionDuringInitialization(ClassName requestingClass) { 178 if (componentShard.name().equals(requestingClass)) { 179 return CodeBlock.of("$L", parameterName); 180 } else { 181 // requesting this component requirement during initialization of a child component requires 182 // it to be accessed from a field and not the parameter (since it is no longer available) 183 return getExpression(requestingClass); 184 } 185 } 186 187 @Override fieldInitialization(FieldSpec componentField)188 CodeBlock fieldInitialization(FieldSpec componentField) { 189 // Don't checkNotNull here because the parameter may be nullable; if it isn't, the caller 190 // should handle checking that before passing the parameter. 191 return CodeBlock.of("this.$N = $L;", componentField, parameterName); 192 } 193 } 194 } 195