| /* |
| * 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.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Preconditions.checkState; |
| 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 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.langmodel.DaggerTypes; |
| import dagger.internal.codegen.xprocessing.MethodSpecs; |
| import dagger.spi.model.DependencyRequest; |
| import dagger.spi.model.RequestKind; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Optional; |
| import javax.inject.Inject; |
| import javax.lang.model.type.TypeMirror; |
| |
| /** 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 DaggerTypes types; |
| 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, |
| DaggerTypes types) { |
| 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); |
| this.types = types; |
| } |
| |
| /** |
| * 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) { |
| |
| TypeMirror dependencyType = dependencyRequest.key().type().java(); |
| 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(types.erasure(dependencyType)); |
| } |
| |
| 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 MethodSpecs.overriding( |
| componentMethod.methodElement(), graph.componentTypeElement().getType()) |
| .addCode( |
| getRequestRepresentation(request) |
| .getComponentMethodImplementation(componentMethod, componentImplementation)) |
| .build(); |
| } |
| |
| /** 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); |
| } |
| } |