blob: 17aa1dacbdfb09d3e404ed87ab51308b6c7803e0 [file] [log] [blame]
ronshapirob4499112017-08-30 11:34:31 -07001/*
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
17package dagger.internal.codegen;
18
19import static com.google.common.base.Preconditions.checkArgument;
20import static com.google.common.collect.Iterables.getOnlyElement;
21import static dagger.internal.codegen.Accessibility.isTypeAccessibleFrom;
22import static dagger.internal.codegen.CodeBlocks.toParametersCodeBlock;
23import static dagger.internal.codegen.ContributionBinding.Kind.SYNTHETIC_MULTIBOUND_MAP;
24import static dagger.internal.codegen.MapKeys.getMapKeyExpression;
25
26import com.google.common.collect.ImmutableMap;
27import com.google.common.collect.Maps;
28import com.squareup.javapoet.ClassName;
29import com.squareup.javapoet.CodeBlock;
30import dagger.internal.MapBuilder;
31import java.util.Collections;
32import java.util.Map;
33import javax.lang.model.type.TypeMirror;
34import javax.lang.model.util.Elements;
35
36/** A {@link BindingExpression} for multibound maps. */
37final class MapBindingExpression extends SimpleInvocationBindingExpression {
38 /** Maximum number of key-value pairs that can be passed to ImmutableMap.of(K, V, K, V, ...). */
39 private static final int MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS = 5;
40
41 private final ProvisionBinding binding;
42 private final ImmutableMap<DependencyRequest, ContributionBinding> dependencies;
43 private final ComponentBindingExpressions componentBindingExpressions;
44 private final Elements elements;
45
46 MapBindingExpression(
47 ProvisionBinding binding,
48 BindingGraph graph,
49 ComponentBindingExpressions componentBindingExpressions,
50 BindingExpression delegate,
dpba409c6f2017-10-04 09:38:54 -070051 DaggerTypes types,
ronshapirob4499112017-08-30 11:34:31 -070052 Elements elements) {
dpba409c6f2017-10-04 09:38:54 -070053 super(delegate, types);
ronshapirob4499112017-08-30 11:34:31 -070054 ContributionBinding.Kind bindingKind = binding.bindingKind();
55 checkArgument(bindingKind.equals(SYNTHETIC_MULTIBOUND_MAP), bindingKind);
56 this.binding = binding;
57 this.componentBindingExpressions = componentBindingExpressions;
58 this.elements = elements;
59 this.dependencies =
60 Maps.toMap(
61 binding.dependencies(),
ronshapiro0a277fd2017-12-22 08:30:49 -080062 dep -> graph.contributionBindings().get(dep.key()).contributionBinding());
ronshapirob4499112017-08-30 11:34:31 -070063 }
64
65 @Override
bcorso8c3605d2017-12-13 13:36:49 -080066 Expression getInstanceDependencyExpression(ClassName requestingClass) {
ronshapiroba794862017-09-28 11:39:34 -070067 return Expression.create(binding.key().type(), mapExpression(requestingClass));
68 }
69
70 private CodeBlock mapExpression(ClassName requestingClass) {
ronshapirob4499112017-08-30 11:34:31 -070071 // TODO(ronshapiro): We should also make an ImmutableMap version of MapFactory
72 boolean isImmutableMapAvailable = isImmutableMapAvailable();
73 // TODO(ronshapiro, gak): Use Maps.immutableEnumMap() if it's available?
74 if (isImmutableMapAvailable && dependencies.size() <= MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS) {
75 return CodeBlock.builder()
76 .add("$T.", ImmutableMap.class)
77 .add(maybeTypeParameters(requestingClass))
78 .add(
79 "of($L)",
80 dependencies
81 .keySet()
82 .stream()
83 .map(dependency -> keyAndValueExpression(dependency, requestingClass))
84 .collect(toParametersCodeBlock()))
85 .build();
86 }
87 switch (dependencies.size()) {
88 case 0:
89 return collectionsStaticFactoryInvocation(requestingClass, CodeBlock.of("emptyMap()"));
90 case 1:
91 return collectionsStaticFactoryInvocation(
92 requestingClass,
93 CodeBlock.of(
94 "singletonMap($L)",
95 keyAndValueExpression(getOnlyElement(dependencies.keySet()), requestingClass)));
96 default:
97 CodeBlock.Builder instantiation = CodeBlock.builder();
98 instantiation
99 .add("$T.", isImmutableMapAvailable ? ImmutableMap.class : MapBuilder.class)
100 .add(maybeTypeParameters(requestingClass));
101 if (isImmutableMapAvailable) {
102 // TODO(ronshapiro): builderWithExpectedSize
103 instantiation.add("builder()");
104 } else {
105 instantiation.add("newMapBuilder($L)", dependencies.size());
106 }
107 for (DependencyRequest dependency : dependencies.keySet()) {
108 instantiation.add(".put($L)", keyAndValueExpression(dependency, requestingClass));
109 }
110 return instantiation.add(".build()").build();
111 }
112 }
113
114 private CodeBlock keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass) {
115 return CodeBlock.of(
116 "$L, $L",
ronshapiroab029392017-09-27 10:40:25 -0700117 getMapKeyExpression(dependencies.get(dependency), requestingClass),
ronshapiroba794862017-09-28 11:39:34 -0700118 componentBindingExpressions
119 .getDependencyExpression(dependency, requestingClass)
120 .codeBlock());
ronshapirob4499112017-08-30 11:34:31 -0700121 }
122
123 private CodeBlock collectionsStaticFactoryInvocation(
124 ClassName requestingClass, CodeBlock methodInvocation) {
125 return CodeBlock.builder()
126 .add("$T.", Collections.class)
127 .add(maybeTypeParameters(requestingClass))
128 .add(methodInvocation)
129 .build();
130 }
131
132 private CodeBlock maybeTypeParameters(ClassName requestingClass) {
133 TypeMirror bindingKeyType = binding.key().type();
134 MapType mapType = MapType.from(binding.key());
135 return isTypeAccessibleFrom(bindingKeyType, requestingClass.packageName())
136 ? CodeBlock.of("<$T, $T>", mapType.keyType(), mapType.valueType())
137 : CodeBlock.of("");
138 }
139
140 private boolean isImmutableMapAvailable() {
141 return elements.getTypeElement(ImmutableMap.class.getCanonicalName()) != null;
142 }
143
144 @Override
145 protected CodeBlock explicitTypeParameter(ClassName requestingClass) {
146 if (isImmutableMapAvailable()) {
147 TypeMirror keyType = binding.key().type();
148 return CodeBlock.of(
149 "<$T>",
150 isTypeAccessibleFrom(keyType, requestingClass.packageName()) ? keyType : Map.class);
151 }
152 return CodeBlock.of("");
153 }
154}