• 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;
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.BindingRequest.bindingRequest;
22 import static dagger.internal.codegen.MapKeys.getMapKeyExpression;
23 import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock;
24 import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom;
25 import static dagger.model.BindingKind.MULTIBOUND_MAP;
26 import static javax.lang.model.util.ElementFilter.methodsIn;
27 
28 import com.google.common.collect.ImmutableMap;
29 import com.google.common.collect.Maps;
30 import com.squareup.javapoet.ClassName;
31 import com.squareup.javapoet.CodeBlock;
32 import dagger.internal.MapBuilder;
33 import dagger.internal.codegen.javapoet.Expression;
34 import dagger.internal.codegen.langmodel.DaggerElements;
35 import dagger.internal.codegen.langmodel.DaggerTypes;
36 import dagger.model.BindingKind;
37 import dagger.model.DependencyRequest;
38 import java.util.Collections;
39 import java.util.Optional;
40 import javax.lang.model.type.DeclaredType;
41 import javax.lang.model.type.TypeMirror;
42 
43 /** A {@link BindingExpression} for multibound maps. */
44 final class MapBindingExpression extends MultibindingExpression {
45   /** Maximum number of key-value pairs that can be passed to ImmutableMap.of(K, V, K, V, ...). */
46   private static final int MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS = 5;
47 
48   private final ProvisionBinding binding;
49   private final ImmutableMap<DependencyRequest, ContributionBinding> dependencies;
50   private final ComponentBindingExpressions componentBindingExpressions;
51   private final DaggerTypes types;
52   private final DaggerElements elements;
53 
MapBindingExpression( ResolvedBindings resolvedBindings, ComponentImplementation componentImplementation, BindingGraph graph, ComponentBindingExpressions componentBindingExpressions, DaggerTypes types, DaggerElements elements)54   MapBindingExpression(
55       ResolvedBindings resolvedBindings,
56       ComponentImplementation componentImplementation,
57       BindingGraph graph,
58       ComponentBindingExpressions componentBindingExpressions,
59       DaggerTypes types,
60       DaggerElements elements) {
61     super(resolvedBindings, componentImplementation);
62     this.binding = (ProvisionBinding) resolvedBindings.contributionBinding();
63     BindingKind bindingKind = this.binding.kind();
64     checkArgument(bindingKind.equals(MULTIBOUND_MAP), bindingKind);
65     this.componentBindingExpressions = componentBindingExpressions;
66     this.types = types;
67     this.elements = elements;
68     this.dependencies =
69         Maps.toMap(
70             binding.dependencies(),
71             dep -> graph.contributionBindings().get(dep.key()).contributionBinding());
72   }
73 
74   @Override
buildDependencyExpression(ClassName requestingClass)75   protected Expression buildDependencyExpression(ClassName requestingClass) {
76     Optional<CodeBlock> superMethodCall = superMethodCall();
77     // TODO(ronshapiro): We should also make an ImmutableMap version of MapFactory
78     boolean isImmutableMapAvailable = isImmutableMapAvailable();
79     // TODO(ronshapiro, gak): Use Maps.immutableEnumMap() if it's available?
80     if (isImmutableMapAvailable
81         && dependencies.size() <= MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS
82         && !superMethodCall.isPresent()) {
83       return Expression.create(
84           immutableMapType(),
85           CodeBlock.builder()
86               .add("$T.", ImmutableMap.class)
87               .add(maybeTypeParameters(requestingClass))
88               .add(
89                   "of($L)",
90                   dependencies
91                       .keySet()
92                       .stream()
93                       .map(dependency -> keyAndValueExpression(dependency, requestingClass))
94                       .collect(toParametersCodeBlock()))
95               .build());
96     }
97     switch (dependencies.size()) {
98       case 0:
99         return collectionsStaticFactoryInvocation(requestingClass, CodeBlock.of("emptyMap()"));
100       case 1:
101         return collectionsStaticFactoryInvocation(
102             requestingClass,
103             CodeBlock.of(
104                 "singletonMap($L)",
105                 keyAndValueExpression(getOnlyElement(dependencies.keySet()), requestingClass)));
106       default:
107         CodeBlock.Builder instantiation = CodeBlock.builder();
108         instantiation
109             .add("$T.", isImmutableMapAvailable ? ImmutableMap.class : MapBuilder.class)
110             .add(maybeTypeParameters(requestingClass));
111         if (isImmutableMapBuilderWithExpectedSizeAvailable()) {
112           instantiation.add("builderWithExpectedSize($L)", dependencies.size());
113         } else if (isImmutableMapAvailable) {
114           instantiation.add("builder()");
115         } else {
116           instantiation.add("newMapBuilder($L)", dependencies.size());
117         }
118         for (DependencyRequest dependency : getNewContributions(dependencies.keySet())) {
119           instantiation.add(".put($L)", keyAndValueExpression(dependency, requestingClass));
120         }
121         if (superMethodCall.isPresent()) {
122           instantiation.add(CodeBlock.of(".putAll($L)", superMethodCall.get()));
123         }
124         return Expression.create(
125             isImmutableMapAvailable ? immutableMapType() : binding.key().type(),
126             instantiation.add(".build()").build());
127     }
128   }
129 
immutableMapType()130   private DeclaredType immutableMapType() {
131     MapType mapType = MapType.from(binding.key());
132     return types.getDeclaredType(
133         elements.getTypeElement(ImmutableMap.class), mapType.keyType(), mapType.valueType());
134   }
135 
keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass)136   private CodeBlock keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass) {
137     return CodeBlock.of(
138         "$L, $L",
139         getMapKeyExpression(dependencies.get(dependency), requestingClass, elements),
140         componentBindingExpressions
141             .getDependencyExpression(bindingRequest(dependency), requestingClass)
142             .codeBlock());
143   }
144 
collectionsStaticFactoryInvocation( ClassName requestingClass, CodeBlock methodInvocation)145   private Expression collectionsStaticFactoryInvocation(
146       ClassName requestingClass, CodeBlock methodInvocation) {
147     return Expression.create(
148         binding.key().type(),
149         CodeBlock.builder()
150             .add("$T.", Collections.class)
151             .add(maybeTypeParameters(requestingClass))
152             .add(methodInvocation)
153             .build());
154   }
155 
maybeTypeParameters(ClassName requestingClass)156   private CodeBlock maybeTypeParameters(ClassName requestingClass) {
157     TypeMirror bindingKeyType = binding.key().type();
158     MapType mapType = MapType.from(binding.key());
159     return isTypeAccessibleFrom(bindingKeyType, requestingClass.packageName())
160         ? CodeBlock.of("<$T, $T>", mapType.keyType(), mapType.valueType())
161         : CodeBlock.of("");
162   }
163 
isImmutableMapBuilderWithExpectedSizeAvailable()164   private boolean isImmutableMapBuilderWithExpectedSizeAvailable() {
165     if (isImmutableMapAvailable()) {
166       return methodsIn(elements.getTypeElement(ImmutableMap.class).getEnclosedElements())
167           .stream()
168           .anyMatch(method -> method.getSimpleName().contentEquals("builderWithExpectedSize"));
169     }
170     return false;
171   }
172 
isImmutableMapAvailable()173   private boolean isImmutableMapAvailable() {
174     return elements.getTypeElement(ImmutableMap.class) != null;
175   }
176 }
177