aboutsummaryrefslogtreecommitdiff
path: root/java/dagger/internal/codegen/writing/ComponentImplementation.java
blob: a09620e8706b4a220d31d202d61599c718d9ef13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
/*
 * 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<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 the subcomponent or subcomponent builder. */
    SUBCOMPONENT
  }

  private ComponentImplementation currentShard = this;
  private final Map<Key, ComponentImplementation> shardsByKey = new HashMap<>();
  private final Optional<ComponentImplementation> 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<CodeBlock> initializations = new ArrayList<>();
  private final List<CodeBlock> componentRequirementInitializations = new ArrayList<>();
  private final Map<ComponentRequirement, String> componentRequirementParameterNames =
      new HashMap<>();
  private final Set<Key> cancellableProducerKeys = new LinkedHashSet<>();
  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 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<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);
  }

  /**
   * 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<CodeBlock> getInitializations() {
    return ImmutableList.copyOf(initializations);
  }

  /**
   * Returns a list of {@link CodeBlock}s for initializing {@link ComponentRequirement}s.
   *
   * <p>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<CodeBlock> getComponentRequirementInitializations() {
    return ImmutableList.copyOf(componentRequirementInitializations);
  }

  /**
   * Returns the list of producer {@link Key}s that need cancellation statements in the cancellation
   * listener method.
   */
  public ImmutableList<Key> 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<Modifier> 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);
  }
}