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