blob: 25e2e001bfda644e26ee6c37e493bee1062af6e0 [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;
ronshapirob4499112017-08-30 11:34:31 -070023import static dagger.internal.codegen.MapKeys.getMapKeyExpression;
ronshapiro0c4cddf2018-01-04 15:28:51 -080024import static dagger.model.BindingKind.MULTIBOUND_MAP;
sunxin84ec3c62018-03-16 12:31:45 -070025import static javax.lang.model.util.ElementFilter.methodsIn;
ronshapirob4499112017-08-30 11:34:31 -070026
27import com.google.common.collect.ImmutableMap;
28import com.google.common.collect.Maps;
29import com.squareup.javapoet.ClassName;
30import com.squareup.javapoet.CodeBlock;
31import dagger.internal.MapBuilder;
ronshapiro0c4cddf2018-01-04 15:28:51 -080032import dagger.model.BindingKind;
ronshapiroa5023ca2018-01-02 12:55:01 -080033import dagger.model.DependencyRequest;
ronshapirob4499112017-08-30 11:34:31 -070034import java.util.Collections;
dpb356a6842018-02-07 07:45:50 -080035import javax.lang.model.type.DeclaredType;
ronshapirob4499112017-08-30 11:34:31 -070036import javax.lang.model.type.TypeMirror;
ronshapirob4499112017-08-30 11:34:31 -070037
38/** A {@link BindingExpression} for multibound maps. */
39final class MapBindingExpression extends SimpleInvocationBindingExpression {
40 /** Maximum number of key-value pairs that can be passed to ImmutableMap.of(K, V, K, V, ...). */
41 private static final int MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS = 5;
42
43 private final ProvisionBinding binding;
44 private final ImmutableMap<DependencyRequest, ContributionBinding> dependencies;
45 private final ComponentBindingExpressions componentBindingExpressions;
dpb356a6842018-02-07 07:45:50 -080046 private final DaggerTypes types;
dpbe7b89582018-02-19 08:42:19 -080047 private final DaggerElements elements;
ronshapirob4499112017-08-30 11:34:31 -070048
49 MapBindingExpression(
dpb09fb2cb2018-01-29 08:39:33 -080050 ResolvedBindings resolvedBindings,
ronshapirob4499112017-08-30 11:34:31 -070051 BindingGraph graph,
52 ComponentBindingExpressions componentBindingExpressions,
dpba409c6f2017-10-04 09:38:54 -070053 DaggerTypes types,
dpbe7b89582018-02-19 08:42:19 -080054 DaggerElements elements) {
dpb356a6842018-02-07 07:45:50 -080055 super(resolvedBindings);
dpb09fb2cb2018-01-29 08:39:33 -080056 this.binding = (ProvisionBinding) resolvedBindings.contributionBinding();
57 BindingKind bindingKind = this.binding.kind();
ronshapiro0c4cddf2018-01-04 15:28:51 -080058 checkArgument(bindingKind.equals(MULTIBOUND_MAP), bindingKind);
ronshapirob4499112017-08-30 11:34:31 -070059 this.componentBindingExpressions = componentBindingExpressions;
dpb356a6842018-02-07 07:45:50 -080060 this.types = types;
ronshapirob4499112017-08-30 11:34:31 -070061 this.elements = elements;
62 this.dependencies =
63 Maps.toMap(
64 binding.dependencies(),
ronshapiro0a277fd2017-12-22 08:30:49 -080065 dep -> graph.contributionBindings().get(dep.key()).contributionBinding());
ronshapirob4499112017-08-30 11:34:31 -070066 }
67
68 @Override
dpb356a6842018-02-07 07:45:50 -080069 Expression getDependencyExpression(ClassName requestingClass) {
ronshapirob4499112017-08-30 11:34:31 -070070 // TODO(ronshapiro): We should also make an ImmutableMap version of MapFactory
71 boolean isImmutableMapAvailable = isImmutableMapAvailable();
72 // TODO(ronshapiro, gak): Use Maps.immutableEnumMap() if it's available?
73 if (isImmutableMapAvailable && dependencies.size() <= MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS) {
dpb356a6842018-02-07 07:45:50 -080074 return Expression.create(
75 immutableMapType(),
76 CodeBlock.builder()
77 .add("$T.", ImmutableMap.class)
78 .add(maybeTypeParameters(requestingClass))
79 .add(
80 "of($L)",
81 dependencies
82 .keySet()
83 .stream()
84 .map(dependency -> keyAndValueExpression(dependency, requestingClass))
85 .collect(toParametersCodeBlock()))
86 .build());
ronshapirob4499112017-08-30 11:34:31 -070087 }
88 switch (dependencies.size()) {
89 case 0:
90 return collectionsStaticFactoryInvocation(requestingClass, CodeBlock.of("emptyMap()"));
91 case 1:
92 return collectionsStaticFactoryInvocation(
93 requestingClass,
94 CodeBlock.of(
95 "singletonMap($L)",
96 keyAndValueExpression(getOnlyElement(dependencies.keySet()), requestingClass)));
97 default:
98 CodeBlock.Builder instantiation = CodeBlock.builder();
99 instantiation
100 .add("$T.", isImmutableMapAvailable ? ImmutableMap.class : MapBuilder.class)
101 .add(maybeTypeParameters(requestingClass));
sunxin84ec3c62018-03-16 12:31:45 -0700102 if (isImmutableMapBuilderWithExpectedSizeAvailable()) {
103 instantiation.add("builderWithExpectedSize($L)", dependencies.size());
104 } else if (isImmutableMapAvailable) {
ronshapirob4499112017-08-30 11:34:31 -0700105 instantiation.add("builder()");
106 } else {
107 instantiation.add("newMapBuilder($L)", dependencies.size());
108 }
109 for (DependencyRequest dependency : dependencies.keySet()) {
110 instantiation.add(".put($L)", keyAndValueExpression(dependency, requestingClass));
111 }
dpb356a6842018-02-07 07:45:50 -0800112 return Expression.create(
113 isImmutableMapAvailable ? immutableMapType() : binding.key().type(),
114 instantiation.add(".build()").build());
ronshapirob4499112017-08-30 11:34:31 -0700115 }
116 }
117
dpb356a6842018-02-07 07:45:50 -0800118 private DeclaredType immutableMapType() {
119 MapType mapType = MapType.from(binding.key());
120 return types.getDeclaredType(
dpbe7b89582018-02-19 08:42:19 -0800121 elements.getTypeElement(ImmutableMap.class), mapType.keyType(), mapType.valueType());
dpb356a6842018-02-07 07:45:50 -0800122 }
123
ronshapirob4499112017-08-30 11:34:31 -0700124 private CodeBlock keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass) {
125 return CodeBlock.of(
126 "$L, $L",
ronshapiro3a089642018-07-17 15:39:40 -0700127 getMapKeyExpression(dependencies.get(dependency), requestingClass, elements),
ronshapiroba794862017-09-28 11:39:34 -0700128 componentBindingExpressions
129 .getDependencyExpression(dependency, requestingClass)
130 .codeBlock());
ronshapirob4499112017-08-30 11:34:31 -0700131 }
132
dpb356a6842018-02-07 07:45:50 -0800133 private Expression collectionsStaticFactoryInvocation(
ronshapirob4499112017-08-30 11:34:31 -0700134 ClassName requestingClass, CodeBlock methodInvocation) {
dpb356a6842018-02-07 07:45:50 -0800135 return Expression.create(
136 binding.key().type(),
137 CodeBlock.builder()
138 .add("$T.", Collections.class)
139 .add(maybeTypeParameters(requestingClass))
140 .add(methodInvocation)
141 .build());
ronshapirob4499112017-08-30 11:34:31 -0700142 }
143
144 private CodeBlock maybeTypeParameters(ClassName requestingClass) {
145 TypeMirror bindingKeyType = binding.key().type();
146 MapType mapType = MapType.from(binding.key());
147 return isTypeAccessibleFrom(bindingKeyType, requestingClass.packageName())
148 ? CodeBlock.of("<$T, $T>", mapType.keyType(), mapType.valueType())
149 : CodeBlock.of("");
150 }
151
sunxin84ec3c62018-03-16 12:31:45 -0700152 private boolean isImmutableMapBuilderWithExpectedSizeAvailable() {
153 if (isImmutableMapAvailable()) {
154 return methodsIn(elements.getTypeElement(ImmutableMap.class).getEnclosedElements())
155 .stream()
156 .anyMatch(method -> method.getSimpleName().contentEquals("builderWithExpectedSize"));
157 }
158 return false;
159 }
160
ronshapirob4499112017-08-30 11:34:31 -0700161 private boolean isImmutableMapAvailable() {
dpbe7b89582018-02-19 08:42:19 -0800162 return elements.getTypeElement(ImmutableMap.class) != null;
ronshapirob4499112017-08-30 11:34:31 -0700163 }
ronshapirob4499112017-08-30 11:34:31 -0700164}