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