blob: b5fe890958a90b163b1bc79d81310c275cadb177 [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;
dpb3379b952018-09-24 13:39:04 -070022import static dagger.internal.codegen.BindingRequest.bindingRequest;
ronshapirob4499112017-08-30 11:34:31 -070023import static dagger.internal.codegen.CodeBlocks.toParametersCodeBlock;
ronshapirob4499112017-08-30 11:34:31 -070024import static dagger.internal.codegen.MapKeys.getMapKeyExpression;
ronshapiro0c4cddf2018-01-04 15:28:51 -080025import static dagger.model.BindingKind.MULTIBOUND_MAP;
sunxin84ec3c62018-03-16 12:31:45 -070026import static javax.lang.model.util.ElementFilter.methodsIn;
ronshapirob4499112017-08-30 11:34:31 -070027
28import com.google.common.collect.ImmutableMap;
29import com.google.common.collect.Maps;
30import com.squareup.javapoet.ClassName;
31import com.squareup.javapoet.CodeBlock;
32import dagger.internal.MapBuilder;
ronshapiro0c4cddf2018-01-04 15:28:51 -080033import dagger.model.BindingKind;
ronshapiroa5023ca2018-01-02 12:55:01 -080034import dagger.model.DependencyRequest;
ronshapirob4499112017-08-30 11:34:31 -070035import java.util.Collections;
dstrasburg09adeae2018-09-07 12:11:06 -070036import java.util.Optional;
dpb356a6842018-02-07 07:45:50 -080037import javax.lang.model.type.DeclaredType;
ronshapirob4499112017-08-30 11:34:31 -070038import javax.lang.model.type.TypeMirror;
ronshapirob4499112017-08-30 11:34:31 -070039
40/** A {@link BindingExpression} for multibound maps. */
dstrasburg09adeae2018-09-07 12:11:06 -070041final class MapBindingExpression extends MultibindingExpression {
ronshapirob4499112017-08-30 11:34:31 -070042 /** Maximum number of key-value pairs that can be passed to ImmutableMap.of(K, V, K, V, ...). */
43 private static final int MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS = 5;
44
45 private final ProvisionBinding binding;
46 private final ImmutableMap<DependencyRequest, ContributionBinding> dependencies;
47 private final ComponentBindingExpressions componentBindingExpressions;
dpb356a6842018-02-07 07:45:50 -080048 private final DaggerTypes types;
dpbe7b89582018-02-19 08:42:19 -080049 private final DaggerElements elements;
ronshapirob4499112017-08-30 11:34:31 -070050
51 MapBindingExpression(
dpb09fb2cb2018-01-29 08:39:33 -080052 ResolvedBindings resolvedBindings,
dstrasburg09adeae2018-09-07 12:11:06 -070053 GeneratedComponentModel generatedComponentModel,
ronshapirob4499112017-08-30 11:34:31 -070054 BindingGraph graph,
55 ComponentBindingExpressions componentBindingExpressions,
dpba409c6f2017-10-04 09:38:54 -070056 DaggerTypes types,
dpbe7b89582018-02-19 08:42:19 -080057 DaggerElements elements) {
dstrasburg09adeae2018-09-07 12:11:06 -070058 super(resolvedBindings, generatedComponentModel);
dpb09fb2cb2018-01-29 08:39:33 -080059 this.binding = (ProvisionBinding) resolvedBindings.contributionBinding();
60 BindingKind bindingKind = this.binding.kind();
ronshapiro0c4cddf2018-01-04 15:28:51 -080061 checkArgument(bindingKind.equals(MULTIBOUND_MAP), bindingKind);
ronshapirob4499112017-08-30 11:34:31 -070062 this.componentBindingExpressions = componentBindingExpressions;
dpb356a6842018-02-07 07:45:50 -080063 this.types = types;
ronshapirob4499112017-08-30 11:34:31 -070064 this.elements = elements;
65 this.dependencies =
66 Maps.toMap(
67 binding.dependencies(),
ronshapiro0a277fd2017-12-22 08:30:49 -080068 dep -> graph.contributionBindings().get(dep.key()).contributionBinding());
ronshapirob4499112017-08-30 11:34:31 -070069 }
70
71 @Override
dstrasburg09adeae2018-09-07 12:11:06 -070072 protected Expression buildDependencyExpression(ClassName requestingClass) {
73 Optional<CodeBlock> superMethodCall = superMethodCall();
ronshapirob4499112017-08-30 11:34:31 -070074 // TODO(ronshapiro): We should also make an ImmutableMap version of MapFactory
75 boolean isImmutableMapAvailable = isImmutableMapAvailable();
76 // TODO(ronshapiro, gak): Use Maps.immutableEnumMap() if it's available?
dstrasburg09adeae2018-09-07 12:11:06 -070077 if (isImmutableMapAvailable
78 && dependencies.size() <= MAX_IMMUTABLE_MAP_OF_KEY_VALUE_PAIRS
79 && !superMethodCall.isPresent()) {
dpb356a6842018-02-07 07:45:50 -080080 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());
ronshapirob4499112017-08-30 11:34:31 -070093 }
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));
sunxin84ec3c62018-03-16 12:31:45 -0700108 if (isImmutableMapBuilderWithExpectedSizeAvailable()) {
109 instantiation.add("builderWithExpectedSize($L)", dependencies.size());
110 } else if (isImmutableMapAvailable) {
ronshapirob4499112017-08-30 11:34:31 -0700111 instantiation.add("builder()");
112 } else {
113 instantiation.add("newMapBuilder($L)", dependencies.size());
114 }
dstrasburg09adeae2018-09-07 12:11:06 -0700115 for (DependencyRequest dependency : getNewContributions(dependencies.keySet())) {
ronshapirob4499112017-08-30 11:34:31 -0700116 instantiation.add(".put($L)", keyAndValueExpression(dependency, requestingClass));
117 }
dstrasburg09adeae2018-09-07 12:11:06 -0700118 if (superMethodCall.isPresent()) {
119 instantiation.add(CodeBlock.of(".putAll($L)", superMethodCall.get()));
120 }
dpb356a6842018-02-07 07:45:50 -0800121 return Expression.create(
122 isImmutableMapAvailable ? immutableMapType() : binding.key().type(),
123 instantiation.add(".build()").build());
ronshapirob4499112017-08-30 11:34:31 -0700124 }
125 }
126
dpb356a6842018-02-07 07:45:50 -0800127 private DeclaredType immutableMapType() {
128 MapType mapType = MapType.from(binding.key());
129 return types.getDeclaredType(
dpbe7b89582018-02-19 08:42:19 -0800130 elements.getTypeElement(ImmutableMap.class), mapType.keyType(), mapType.valueType());
dpb356a6842018-02-07 07:45:50 -0800131 }
132
ronshapirob4499112017-08-30 11:34:31 -0700133 private CodeBlock keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass) {
134 return CodeBlock.of(
135 "$L, $L",
ronshapiro3a089642018-07-17 15:39:40 -0700136 getMapKeyExpression(dependencies.get(dependency), requestingClass, elements),
ronshapiroba794862017-09-28 11:39:34 -0700137 componentBindingExpressions
dpb3379b952018-09-24 13:39:04 -0700138 .getDependencyExpression(bindingRequest(dependency), requestingClass)
ronshapiroba794862017-09-28 11:39:34 -0700139 .codeBlock());
ronshapirob4499112017-08-30 11:34:31 -0700140 }
141
dpb356a6842018-02-07 07:45:50 -0800142 private Expression collectionsStaticFactoryInvocation(
ronshapirob4499112017-08-30 11:34:31 -0700143 ClassName requestingClass, CodeBlock methodInvocation) {
dpb356a6842018-02-07 07:45:50 -0800144 return Expression.create(
145 binding.key().type(),
146 CodeBlock.builder()
147 .add("$T.", Collections.class)
148 .add(maybeTypeParameters(requestingClass))
149 .add(methodInvocation)
150 .build());
ronshapirob4499112017-08-30 11:34:31 -0700151 }
152
153 private CodeBlock maybeTypeParameters(ClassName requestingClass) {
154 TypeMirror bindingKeyType = binding.key().type();
155 MapType mapType = MapType.from(binding.key());
156 return isTypeAccessibleFrom(bindingKeyType, requestingClass.packageName())
157 ? CodeBlock.of("<$T, $T>", mapType.keyType(), mapType.valueType())
158 : CodeBlock.of("");
159 }
160
sunxin84ec3c62018-03-16 12:31:45 -0700161 private boolean isImmutableMapBuilderWithExpectedSizeAvailable() {
162 if (isImmutableMapAvailable()) {
163 return methodsIn(elements.getTypeElement(ImmutableMap.class).getEnclosedElements())
164 .stream()
165 .anyMatch(method -> method.getSimpleName().contentEquals("builderWithExpectedSize"));
166 }
167 return false;
168 }
169
ronshapirob4499112017-08-30 11:34:31 -0700170 private boolean isImmutableMapAvailable() {
dpbe7b89582018-02-19 08:42:19 -0800171 return elements.getTypeElement(ImmutableMap.class) != null;
ronshapirob4499112017-08-30 11:34:31 -0700172 }
ronshapirob4499112017-08-30 11:34:31 -0700173}