• 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.collect.Iterables.getOnlyElement;
21 import static dagger.internal.codegen.binding.BindingRequest.bindingRequest;
22 import static dagger.internal.codegen.binding.MapKeys.getMapKeyExpression;
23 import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock;
24 import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom;
25 import static dagger.internal.codegen.model.BindingKind.MULTIBOUND_MAP;
26 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
27 
28 import androidx.room.compiler.processing.XProcessingEnv;
29 import androidx.room.compiler.processing.XType;
30 import com.google.common.collect.ImmutableMap;
31 import com.google.common.collect.Maps;
32 import com.squareup.javapoet.ClassName;
33 import com.squareup.javapoet.CodeBlock;
34 import dagger.assisted.Assisted;
35 import dagger.assisted.AssistedFactory;
36 import dagger.assisted.AssistedInject;
37 import dagger.internal.MapBuilder;
38 import dagger.internal.codegen.base.MapType;
39 import dagger.internal.codegen.binding.BindingGraph;
40 import dagger.internal.codegen.binding.ContributionBinding;
41 import dagger.internal.codegen.binding.MapKeys;
42 import dagger.internal.codegen.binding.ProvisionBinding;
43 import dagger.internal.codegen.javapoet.Expression;
44 import dagger.internal.codegen.javapoet.TypeNames;
45 import dagger.internal.codegen.model.BindingKind;
46 import dagger.internal.codegen.model.DependencyRequest;
47 import java.util.Collections;
48 
49 /** A {@link RequestRepresentation} for multibound maps. */
50 final class MapRequestRepresentation extends RequestRepresentation {
51   /** Maximum number of key-value pairs that can be passed to ImmutableMap.of(K, V, K, V, ...). */
52   private static final int MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS = 5;
53 
54   private final XProcessingEnv processingEnv;
55   private final ProvisionBinding binding;
56   private final ImmutableMap<DependencyRequest, ContributionBinding> dependencies;
57   private final ComponentRequestRepresentations componentRequestRepresentations;
58   private final boolean useLazyClassKey;
59   private final LazyClassKeyProviders lazyClassKeyProviders;
60 
61   @AssistedInject
MapRequestRepresentation( @ssisted ProvisionBinding binding, XProcessingEnv processingEnv, BindingGraph graph, ComponentImplementation componentImplementation, ComponentRequestRepresentations componentRequestRepresentations)62   MapRequestRepresentation(
63       @Assisted ProvisionBinding binding,
64       XProcessingEnv processingEnv,
65       BindingGraph graph,
66       ComponentImplementation componentImplementation,
67       ComponentRequestRepresentations componentRequestRepresentations) {
68     this.binding = binding;
69     this.processingEnv = processingEnv;
70     BindingKind bindingKind = this.binding.kind();
71     checkArgument(bindingKind.equals(MULTIBOUND_MAP), bindingKind);
72     this.componentRequestRepresentations = componentRequestRepresentations;
73     this.dependencies =
74         Maps.toMap(binding.dependencies(), dep -> graph.contributionBinding(dep.key()));
75     this.useLazyClassKey = MapKeys.useLazyClassKey(binding, graph);
76     this.lazyClassKeyProviders =
77         componentImplementation.shardImplementation(binding).getLazyClassKeyProviders();
78   }
79 
80   @Override
getDependencyExpression(ClassName requestingClass)81   Expression getDependencyExpression(ClassName requestingClass) {
82     MapType mapType = MapType.from(binding.key());
83     Expression dependencyExpression = getUnderlyingMapExpression(requestingClass);
84     // LazyClassKey is backed with a string map, therefore needs to be wrapped.
85     if (useLazyClassKey) {
86       return Expression.create(
87           dependencyExpression.type(),
88           CodeBlock.of(
89               "$T.<$T>of($L)",
90               TypeNames.LAZY_CLASS_KEY_MAP,
91               mapType.valueType().getTypeName(),
92               dependencyExpression.codeBlock()));
93     }
94     return dependencyExpression;
95   }
96 
getUnderlyingMapExpression(ClassName requestingClass)97   private Expression getUnderlyingMapExpression(ClassName requestingClass) {
98     // TODO(ronshapiro): We should also make an ImmutableMap version of MapFactory
99     boolean isImmutableMapAvailable = isImmutableMapAvailable();
100     // TODO(ronshapiro, gak): Use Maps.immutableEnumMap() if it's available?
101     if (isImmutableMapAvailable && dependencies.size() <= MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS) {
102       return Expression.create(
103           immutableMapType(),
104           CodeBlock.builder()
105               .add("$T.", ImmutableMap.class)
106               .add(maybeTypeParameters(requestingClass))
107               .add(
108                   "of($L)",
109                   dependencies.keySet().stream()
110                       .map(dependency -> keyAndValueExpression(dependency, requestingClass))
111                       .collect(toParametersCodeBlock()))
112               .build());
113     }
114     switch (dependencies.size()) {
115       case 0:
116         return collectionsStaticFactoryInvocation(requestingClass, CodeBlock.of("emptyMap()"));
117       case 1:
118         return collectionsStaticFactoryInvocation(
119             requestingClass,
120             CodeBlock.of(
121                 "singletonMap($L)",
122                 keyAndValueExpression(getOnlyElement(dependencies.keySet()), requestingClass)));
123       default:
124         CodeBlock.Builder instantiation =
125             CodeBlock.builder()
126                 .add("$T.", isImmutableMapAvailable ? ImmutableMap.class : MapBuilder.class)
127                 .add(maybeTypeParameters(requestingClass));
128         if (isImmutableMapBuilderWithExpectedSizeAvailable()) {
129           instantiation.add("builderWithExpectedSize($L)", dependencies.size());
130         } else if (isImmutableMapAvailable) {
131           instantiation.add("builder()");
132         } else {
133           instantiation.add("newMapBuilder($L)", dependencies.size());
134         }
135         for (DependencyRequest dependency : dependencies.keySet()) {
136           instantiation.add(".put($L)", keyAndValueExpression(dependency, requestingClass));
137         }
138         return Expression.create(
139             isImmutableMapAvailable ? immutableMapType() : binding.key().type().xprocessing(),
140             instantiation.add(".build()").build());
141     }
142   }
143 
immutableMapType()144   private XType immutableMapType() {
145     MapType mapType = MapType.from(binding.key());
146     return processingEnv.getDeclaredType(
147         processingEnv.requireTypeElement(TypeNames.IMMUTABLE_MAP),
148         mapType.keyType(),
149         mapType.valueType());
150   }
151 
keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass)152   private CodeBlock keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass) {
153     return CodeBlock.of(
154         "$L, $L",
155         useLazyClassKey
156             ? lazyClassKeyProviders.getMapKeyExpression(dependency.key())
157             : getMapKeyExpression(dependencies.get(dependency), requestingClass, processingEnv),
158         componentRequestRepresentations
159             .getDependencyExpression(bindingRequest(dependency), requestingClass)
160             .codeBlock());
161   }
162 
collectionsStaticFactoryInvocation( ClassName requestingClass, CodeBlock methodInvocation)163   private Expression collectionsStaticFactoryInvocation(
164       ClassName requestingClass, CodeBlock methodInvocation) {
165     return Expression.create(
166         binding.key().type().xprocessing(),
167         CodeBlock.builder()
168             .add("$T.", Collections.class)
169             .add(maybeTypeParameters(requestingClass))
170             .add(methodInvocation)
171             .build());
172   }
173 
maybeTypeParameters(ClassName requestingClass)174   private CodeBlock maybeTypeParameters(ClassName requestingClass) {
175     XType bindingKeyType = binding.key().type().xprocessing();
176     MapType mapType = MapType.from(binding.key());
177     return isTypeAccessibleFrom(bindingKeyType, requestingClass.packageName())
178         ? CodeBlock.of(
179             "<$T, $T>",
180             useLazyClassKey ? TypeNames.STRING : mapType.keyType().getTypeName(),
181             mapType.valueType().getTypeName())
182         : CodeBlock.of("");
183   }
184 
isImmutableMapBuilderWithExpectedSizeAvailable()185   private boolean isImmutableMapBuilderWithExpectedSizeAvailable() {
186     return isImmutableMapAvailable()
187         && processingEnv.requireTypeElement(TypeNames.IMMUTABLE_MAP).getDeclaredMethods().stream()
188             .anyMatch(method -> getSimpleName(method).contentEquals("builderWithExpectedSize"));
189   }
190 
isImmutableMapAvailable()191   private boolean isImmutableMapAvailable() {
192     return processingEnv.findTypeElement(TypeNames.IMMUTABLE_MAP) != null;
193   }
194 
195   @AssistedFactory
196   static interface Factory {
create(ProvisionBinding binding)197     MapRequestRepresentation create(ProvisionBinding binding);
198   }
199 }
200