• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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