aboutsummaryrefslogtreecommitdiff
path: root/java/dagger/internal/codegen/writing/ComponentRequestRepresentations.java
blob: 124eba1ff59c494e276045f9fdd727d2e50ff269 (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
/*
 * 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 androidx.room.compiler.processing.XTypeKt.isVoid;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
import static dagger.internal.codegen.binding.BindingRequest.bindingRequest;
import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock;
import static dagger.internal.codegen.langmodel.Accessibility.isRawTypeAccessible;
import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom;
import static dagger.internal.codegen.xprocessing.MethodSpecs.overriding;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;

import androidx.room.compiler.processing.XMethodElement;
import androidx.room.compiler.processing.XType;
import com.google.common.collect.ImmutableList;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
import dagger.internal.codegen.binding.Binding;
import dagger.internal.codegen.binding.BindingGraph;
import dagger.internal.codegen.binding.BindingRequest;
import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor;
import dagger.internal.codegen.binding.ComponentRequirement;
import dagger.internal.codegen.binding.ContributionBinding;
import dagger.internal.codegen.binding.FrameworkType;
import dagger.internal.codegen.binding.FrameworkTypeMapper;
import dagger.internal.codegen.binding.MembersInjectionBinding;
import dagger.internal.codegen.binding.ProductionBinding;
import dagger.internal.codegen.binding.ProvisionBinding;
import dagger.internal.codegen.javapoet.Expression;
import dagger.internal.codegen.model.DependencyRequest;
import dagger.internal.codegen.model.RequestKind;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;

/** A central repository of code expressions used to access any binding available to a component. */
@PerComponentImplementation
public final class ComponentRequestRepresentations {
  // TODO(dpb,ronshapiro): refactor this and ComponentRequirementExpressions into a
  // HierarchicalComponentMap<K, V>, or perhaps this use a flattened ImmutableMap, built from its
  // parents? If so, maybe make RequestRepresentation.Factory create it.

  private final Optional<ComponentRequestRepresentations> parent;
  private final BindingGraph graph;
  private final ComponentImplementation componentImplementation;
  private final ComponentRequirementExpressions componentRequirementExpressions;
  private final MembersInjectionBindingRepresentation.Factory
      membersInjectionBindingRepresentationFactory;
  private final ProvisionBindingRepresentation.Factory provisionBindingRepresentationFactory;
  private final ProductionBindingRepresentation.Factory productionBindingRepresentationFactory;
  private final ExperimentalSwitchingProviderDependencyRepresentation.Factory
      experimentalSwitchingProviderDependencyRepresentationFactory;
  private final Map<Binding, BindingRepresentation> representations = new HashMap<>();
  private final Map<Binding, ExperimentalSwitchingProviderDependencyRepresentation>
      experimentalSwitchingProviderDependencyRepresentations = new HashMap<>();

  @Inject
  ComponentRequestRepresentations(
      @ParentComponent Optional<ComponentRequestRepresentations> parent,
      BindingGraph graph,
      ComponentImplementation componentImplementation,
      ComponentRequirementExpressions componentRequirementExpressions,
      MembersInjectionBindingRepresentation.Factory membersInjectionBindingRepresentationFactory,
      ProvisionBindingRepresentation.Factory provisionBindingRepresentationFactory,
      ProductionBindingRepresentation.Factory productionBindingRepresentationFactory,
      ExperimentalSwitchingProviderDependencyRepresentation.Factory
          experimentalSwitchingProviderDependencyRepresentationFactory) {
    this.parent = parent;
    this.graph = graph;
    this.componentImplementation = componentImplementation;
    this.membersInjectionBindingRepresentationFactory =
        membersInjectionBindingRepresentationFactory;
    this.provisionBindingRepresentationFactory = provisionBindingRepresentationFactory;
    this.productionBindingRepresentationFactory = productionBindingRepresentationFactory;
    this.experimentalSwitchingProviderDependencyRepresentationFactory =
        experimentalSwitchingProviderDependencyRepresentationFactory;
    this.componentRequirementExpressions = checkNotNull(componentRequirementExpressions);
  }

  /**
   * Returns an expression that evaluates to the value of a binding request for a binding owned by
   * this component or an ancestor.
   *
   * @param requestingClass the class that will contain the expression
   * @throws IllegalStateException if there is no binding expression that satisfies the request
   */
  public Expression getDependencyExpression(BindingRequest request, ClassName requestingClass) {
    return getRequestRepresentation(request).getDependencyExpression(requestingClass);
  }

  /**
   * Equivalent to {@link #getDependencyExpression(BindingRequest, ClassName)} that is used only
   * when the request is for implementation of a component method.
   *
   * @throws IllegalStateException if there is no binding expression that satisfies the request
   */
  Expression getDependencyExpressionForComponentMethod(
      BindingRequest request,
      ComponentMethodDescriptor componentMethod,
      ComponentImplementation componentImplementation) {
    return getRequestRepresentation(request)
        .getDependencyExpressionForComponentMethod(componentMethod, componentImplementation);
  }

  /**
   * Returns the {@link CodeBlock} for the method arguments used with the factory {@code create()}
   * method for the given {@link ContributionBinding binding}.
   */
  CodeBlock getCreateMethodArgumentsCodeBlock(
      ContributionBinding binding, ClassName requestingClass) {
    return makeParametersCodeBlock(getCreateMethodArgumentsCodeBlocks(binding, requestingClass));
  }

  private ImmutableList<CodeBlock> getCreateMethodArgumentsCodeBlocks(
      ContributionBinding binding, ClassName requestingClass) {
    ImmutableList.Builder<CodeBlock> arguments = ImmutableList.builder();

    if (binding.requiresModuleInstance()) {
      arguments.add(
          componentRequirementExpressions.getExpressionDuringInitialization(
              ComponentRequirement.forModule(binding.contributingModule().get().getType()),
              requestingClass));
    }

    binding.dependencies().stream()
        .map(dependency -> frameworkRequest(binding, dependency))
        .map(request -> getDependencyExpression(request, requestingClass))
        .map(Expression::codeBlock)
        .forEach(arguments::add);

    return arguments.build();
  }

  private static BindingRequest frameworkRequest(
      ContributionBinding binding, DependencyRequest dependency) {
    // TODO(bcorso): See if we can get rid of FrameworkTypeMatcher
    FrameworkType frameworkType =
        FrameworkTypeMapper.forBindingType(binding.bindingType())
            .getFrameworkType(dependency.kind());
    return BindingRequest.bindingRequest(dependency.key(), frameworkType);
  }

  /**
   * Returns an expression that evaluates to the value of a dependency request, for passing to a
   * binding method, an {@code @Inject}-annotated constructor or member, or a proxy for one.
   *
   * <p>If the method is a generated static {@link InjectionMethods injection method}, each
   * parameter will be {@link Object} if the dependency's raw type is inaccessible. If that is the
   * case for this dependency, the returned expression will use a cast to evaluate to the raw type.
   *
   * @param requestingClass the class that will contain the expression
   */
  Expression getDependencyArgumentExpression(
      DependencyRequest dependencyRequest, ClassName requestingClass) {

    XType dependencyType = dependencyRequest.key().type().xprocessing();
    BindingRequest bindingRequest = bindingRequest(dependencyRequest);
    Expression dependencyExpression = getDependencyExpression(bindingRequest, requestingClass);

    if (dependencyRequest.kind().equals(RequestKind.INSTANCE)
        && !isTypeAccessibleFrom(dependencyType, requestingClass.packageName())
        && isRawTypeAccessible(dependencyType, requestingClass.packageName())) {
      return dependencyExpression.castTo(dependencyType.getRawType());
    }

    return dependencyExpression;
  }

  /** Returns the implementation of a component method. */
  public MethodSpec getComponentMethod(ComponentMethodDescriptor componentMethod) {
    checkArgument(componentMethod.dependencyRequest().isPresent());
    BindingRequest request = bindingRequest(componentMethod.dependencyRequest().get());
    return overriding(componentMethod.methodElement(), graph.componentTypeElement().getType())
        .addCode(
            request.isRequestKind(RequestKind.MEMBERS_INJECTION)
                ? getMembersInjectionComponentMethodImplementation(request, componentMethod)
                : getContributionComponentMethodImplementation(request, componentMethod))
        .build();
  }

  private CodeBlock getMembersInjectionComponentMethodImplementation(
      BindingRequest request, ComponentMethodDescriptor componentMethod) {
    checkArgument(request.isRequestKind(RequestKind.MEMBERS_INJECTION));
    XMethodElement methodElement = componentMethod.methodElement();
    RequestRepresentation requestRepresentation = getRequestRepresentation(request);
    MembersInjectionBinding binding =
        ((MembersInjectionRequestRepresentation) requestRepresentation).binding();
    if (binding.injectionSites().isEmpty()) {
      // If there are no injection sites either do nothing (if the return type is void) or return
      // the input instance as-is.
      return isVoid(methodElement.getReturnType())
          ? CodeBlock.of("")
          : CodeBlock.of(
              "return $L;", getSimpleName(getOnlyElement(methodElement.getParameters())));
    }
    Expression expression = getComponentMethodExpression(requestRepresentation, componentMethod);
    return isVoid(methodElement.getReturnType())
        ? CodeBlock.of("$L;", expression.codeBlock())
        : CodeBlock.of("return $L;", expression.codeBlock());
  }

  private CodeBlock getContributionComponentMethodImplementation(
      BindingRequest request, ComponentMethodDescriptor componentMethod) {
    checkArgument(!request.isRequestKind(RequestKind.MEMBERS_INJECTION));
    Expression expression =
        getComponentMethodExpression(getRequestRepresentation(request), componentMethod);
    return CodeBlock.of("return $L;", expression.codeBlock());
  }

  private Expression getComponentMethodExpression(
      RequestRepresentation requestRepresentation, ComponentMethodDescriptor componentMethod) {
    Expression expression =
        requestRepresentation.getDependencyExpressionForComponentMethod(
            componentMethod, componentImplementation);

    // Cast if the expression type does not match the component method's return type. This is useful
    // for types that have protected accessibility to the component but are not accessible to other
    // classes, e.g. shards, that may need to handle the implementation of the binding.
    XType returnType =
        componentMethod.methodElement()
            .asMemberOf(componentImplementation.graph().componentTypeElement().getType())
            .getReturnType();
    return !isVoid(returnType) && !expression.type().isAssignableTo(returnType)
        ? expression.castTo(returnType)
        : expression;
  }

  /** Returns the {@link RequestRepresentation} for the given {@link BindingRequest}. */
  RequestRepresentation getRequestRepresentation(BindingRequest request) {
    Optional<Binding> localBinding =
        request.isRequestKind(RequestKind.MEMBERS_INJECTION)
            ? graph.localMembersInjectionBinding(request.key())
            : graph.localContributionBinding(request.key());

    if (localBinding.isPresent()) {
      return getBindingRepresentation(localBinding.get()).getRequestRepresentation(request);
    }

    checkArgument(parent.isPresent(), "no expression found for %s", request);
    return parent.get().getRequestRepresentation(request);
  }

  private BindingRepresentation getBindingRepresentation(Binding binding) {
    return reentrantComputeIfAbsent(
        representations, binding, this::getBindingRepresentationUncached);
  }

  private BindingRepresentation getBindingRepresentationUncached(Binding binding) {
    switch (binding.bindingType()) {
      case MEMBERS_INJECTION:
        return membersInjectionBindingRepresentationFactory.create(
            (MembersInjectionBinding) binding);
      case PROVISION:
        return provisionBindingRepresentationFactory.create((ProvisionBinding) binding);
      case PRODUCTION:
        return productionBindingRepresentationFactory.create((ProductionBinding) binding);
    }
    throw new AssertionError();
  }

  /**
   * Returns an {@link ExperimentalSwitchingProviderDependencyRepresentation} for the requested
   * binding to satisfy dependency requests on it from experimental switching providers. Cannot be
   * used for Members Injection requests.
   */
  ExperimentalSwitchingProviderDependencyRepresentation
      getExperimentalSwitchingProviderDependencyRepresentation(BindingRequest request) {
    checkState(
        componentImplementation.compilerMode().isExperimentalMergedMode(),
        "Compiler mode should be experimentalMergedMode!");
    Optional<Binding> localBinding = graph.localContributionBinding(request.key());

    if (localBinding.isPresent()) {
      return reentrantComputeIfAbsent(
          experimentalSwitchingProviderDependencyRepresentations,
          localBinding.get(),
          binding ->
              experimentalSwitchingProviderDependencyRepresentationFactory.create(
                  (ProvisionBinding) binding));
    }

    checkArgument(parent.isPresent(), "no expression found for %s", request);
    return parent.get().getExperimentalSwitchingProviderDependencyRepresentation(request);
  }
}