blob: e8edefc87022d8211551f220a2b25795fcafdb43 [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;
ronshapirob4499112017-08-30 11:34:31 -070025
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;
ronshapiro0c4cddf2018-01-04 15:28:51 -080031import dagger.model.BindingKind;
ronshapiroa5023ca2018-01-02 12:55:01 -080032import dagger.model.DependencyRequest;
ronshapirob4499112017-08-30 11:34:31 -070033import java.util.Collections;
dpb356a6842018-02-07 07:45:50 -080034import javax.lang.model.type.DeclaredType;
ronshapirob4499112017-08-30 11:34:31 -070035import javax.lang.model.type.TypeMirror;
36import javax.lang.model.util.Elements;
37
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;
ronshapirob4499112017-08-30 11:34:31 -070047 private final Elements elements;
48
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,
ronshapirob4499112017-08-30 11:34:31 -070054 Elements 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));
102 if (isImmutableMapAvailable) {
103 // TODO(ronshapiro): builderWithExpectedSize
104 instantiation.add("builder()");
105 } else {
106 instantiation.add("newMapBuilder($L)", dependencies.size());
107 }
108 for (DependencyRequest dependency : dependencies.keySet()) {
109 instantiation.add(".put($L)", keyAndValueExpression(dependency, requestingClass));
110 }
dpb356a6842018-02-07 07:45:50 -0800111 return Expression.create(
112 isImmutableMapAvailable ? immutableMapType() : binding.key().type(),
113 instantiation.add(".build()").build());
ronshapirob4499112017-08-30 11:34:31 -0700114 }
115 }
116
dpb356a6842018-02-07 07:45:50 -0800117 private DeclaredType immutableMapType() {
118 MapType mapType = MapType.from(binding.key());
119 return types.getDeclaredType(
120 elements.getTypeElement(ImmutableMap.class.getName()),
121 mapType.keyType(),
122 mapType.valueType());
123 }
124
ronshapirob4499112017-08-30 11:34:31 -0700125 private CodeBlock keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass) {
126 return CodeBlock.of(
127 "$L, $L",
ronshapiroab029392017-09-27 10:40:25 -0700128 getMapKeyExpression(dependencies.get(dependency), requestingClass),
ronshapiroba794862017-09-28 11:39:34 -0700129 componentBindingExpressions
130 .getDependencyExpression(dependency, requestingClass)
131 .codeBlock());
ronshapirob4499112017-08-30 11:34:31 -0700132 }
133
dpb356a6842018-02-07 07:45:50 -0800134 private Expression collectionsStaticFactoryInvocation(
ronshapirob4499112017-08-30 11:34:31 -0700135 ClassName requestingClass, CodeBlock methodInvocation) {
dpb356a6842018-02-07 07:45:50 -0800136 return Expression.create(
137 binding.key().type(),
138 CodeBlock.builder()
139 .add("$T.", Collections.class)
140 .add(maybeTypeParameters(requestingClass))
141 .add(methodInvocation)
142 .build());
ronshapirob4499112017-08-30 11:34:31 -0700143 }
144
145 private CodeBlock maybeTypeParameters(ClassName requestingClass) {
146 TypeMirror bindingKeyType = binding.key().type();
147 MapType mapType = MapType.from(binding.key());
148 return isTypeAccessibleFrom(bindingKeyType, requestingClass.packageName())
149 ? CodeBlock.of("<$T, $T>", mapType.keyType(), mapType.valueType())
150 : CodeBlock.of("");
151 }
152
153 private boolean isImmutableMapAvailable() {
154 return elements.getTypeElement(ImmutableMap.class.getCanonicalName()) != null;
155 }
ronshapirob4499112017-08-30 11:34:31 -0700156}