blob: adb6b54b14b827238af4fc1591511d6738fb431c [file] [log] [blame]
bcorso8a8039a2018-03-19 09:36:26 -07001/*
2 * Copyright (C) 2018 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.checkNotNull;
bcorsoaf9e0042018-04-16 14:12:23 -070020import static com.google.common.collect.Iterables.getLast;
21import static com.google.common.collect.Iterables.getOnlyElement;
bcorso8a8039a2018-03-19 09:36:26 -070022import static com.squareup.javapoet.MethodSpec.methodBuilder;
23import static com.squareup.javapoet.TypeSpec.classBuilder;
24import static dagger.internal.codegen.AnnotationSpecs.Suppression.UNCHECKED;
25import static dagger.internal.codegen.AnnotationSpecs.suppressWarnings;
bcorsoaf9e0042018-04-16 14:12:23 -070026import static dagger.internal.codegen.DaggerStreams.toImmutableList;
bcorso8a8039a2018-03-19 09:36:26 -070027import static dagger.internal.codegen.TypeNames.providerOf;
bcorso8a8039a2018-03-19 09:36:26 -070028import static javax.lang.model.element.Modifier.PRIVATE;
29import static javax.lang.model.element.Modifier.PUBLIC;
30
bcorsoaf9e0042018-04-16 14:12:23 -070031import com.google.common.collect.ImmutableList;
32import com.google.common.collect.Lists;
bcorso8a8039a2018-03-19 09:36:26 -070033import com.squareup.javapoet.ClassName;
34import com.squareup.javapoet.CodeBlock;
bcorsoaf9e0042018-04-16 14:12:23 -070035import com.squareup.javapoet.MethodSpec;
bcorso8a8039a2018-03-19 09:36:26 -070036import com.squareup.javapoet.TypeSpec;
37import com.squareup.javapoet.TypeVariableName;
38import dagger.model.Key;
39import java.util.HashMap;
bcorsoaf9e0042018-04-16 14:12:23 -070040import java.util.LinkedHashMap;
bcorso8a8039a2018-03-19 09:36:26 -070041import java.util.Map;
42import java.util.TreeMap;
bcorso8a8039a2018-03-19 09:36:26 -070043
44/**
45 * Keeps track of all provider expression requests for a component.
46 *
47 * <p>The provider expression request will be satisfied by a single generated {@code Provider} inner
48 * class that can provide instances for all types by switching on an id.
49 */
bcorsocc739fe2018-04-23 09:16:12 -070050abstract class SwitchingProviders {
51 /**
52 * Defines the {@linkplain Expression expressions} for a switch case in a {@code SwitchProvider}
53 * for a particular binding.
54 */
55 // TODO(user): Consider handling SwitchingProviders with dependency arguments in this class,
56 // then we wouldn't need the getProviderExpression method.
57 // TODO(user): Consider making this an abstract class with equals/hashCode defined by the key
58 // and then using this class directly in Map types instead of Key.
59 interface SwitchCase {
60 /** Returns the {@link Key} for this switch case. */
61 Key key();
62
63 /** Returns the {@link Expression} that returns the provided instance for this case. */
bcorsobe9493d2018-10-04 13:52:31 -070064 Expression getReturnExpression(ClassName switchingProviderClass);
bcorsocc739fe2018-04-23 09:16:12 -070065
66 /**
67 * Returns the {@link Expression} that returns the {@code SwitchProvider} instance for this
68 * case.
69 */
bcorsobe9493d2018-10-04 13:52:31 -070070 Expression getProviderExpression(ClassName switchingProviderClass, int switchId);
bcorsocc739fe2018-04-23 09:16:12 -070071 }
72
bcorsoaf9e0042018-04-16 14:12:23 -070073 /**
74 * Each switch size is fixed at 100 cases each and put in its own method. This is to limit the
75 * size of the methods so that we don't reach the "huge" method size limit for Android that will
76 * prevent it from being AOT compiled in some versions of Android (b/77652521). This generally
77 * starts to happen around 1500 cases, but we are choosing 100 to be safe.
78 */
79 // TODO(user): Include a proguard_spec in the Dagger library to prevent inlining these methods?
80 // TODO(ronshapiro): Consider making this configurable via a flag.
81 private static final int MAX_CASES_PER_SWITCH = 100;
82
83 private static final long MAX_CASES_PER_CLASS = MAX_CASES_PER_SWITCH * MAX_CASES_PER_SWITCH;
bcorso8a8039a2018-03-19 09:36:26 -070084 private static final TypeVariableName T = TypeVariableName.get("T");
85
bcorsoaf9e0042018-04-16 14:12:23 -070086 /**
bcorsocc739fe2018-04-23 09:16:12 -070087 * Maps a {@link Key} to an instance of a {@link SwitchingProviderBuilder}. Each group of {@code
88 * MAX_CASES_PER_CLASS} keys will share the same instance.
bcorsoaf9e0042018-04-16 14:12:23 -070089 */
bcorsocc739fe2018-04-23 09:16:12 -070090 private final Map<Key, SwitchingProviderBuilder> switchingProviderBuilders =
bcorsoaf9e0042018-04-16 14:12:23 -070091 new LinkedHashMap<>();
bcorso8a8039a2018-03-19 09:36:26 -070092
dpb159d81b2018-11-01 14:06:17 -070093 private final ComponentImplementation componentImplementation;
bcorso8a8039a2018-03-19 09:36:26 -070094 private final ClassName owningComponent;
bcorso8a8039a2018-03-19 09:36:26 -070095 private final DaggerTypes types;
bcorsoaf9e0042018-04-16 14:12:23 -070096 private final UniqueNameSet switchingProviderNames = new UniqueNameSet();
bcorso8a8039a2018-03-19 09:36:26 -070097
dpb159d81b2018-11-01 14:06:17 -070098 SwitchingProviders(ComponentImplementation componentImplementation, DaggerTypes types) {
99 this.componentImplementation = checkNotNull(componentImplementation);
bcorso8a8039a2018-03-19 09:36:26 -0700100 this.types = checkNotNull(types);
dpb159d81b2018-11-01 14:06:17 -0700101 this.owningComponent = checkNotNull(componentImplementation).name();
bcorso8a8039a2018-03-19 09:36:26 -0700102 }
103
bcorsocc739fe2018-04-23 09:16:12 -0700104 /** Returns the {@link TypeSpec} for a {@code SwitchingProvider} based on the given builder. */
105 protected abstract TypeSpec createSwitchingProviderType(TypeSpec.Builder builder);
106
bcorso8a8039a2018-03-19 09:36:26 -0700107 /**
bcorsocc739fe2018-04-23 09:16:12 -0700108 * Returns the {@link Expression} that returns the {@code SwitchProvider} instance for the case.
bcorso8a8039a2018-03-19 09:36:26 -0700109 */
bcorsocc739fe2018-04-23 09:16:12 -0700110 protected final Expression getProviderExpression(SwitchCase switchCase) {
111 return switchingProviderBuilders
112 .computeIfAbsent(switchCase.key(), key -> getSwitchingProviderBuilder())
113 .getProviderExpression(switchCase);
bcorso8a8039a2018-03-19 09:36:26 -0700114 }
115
bcorsocc739fe2018-04-23 09:16:12 -0700116 private SwitchingProviderBuilder getSwitchingProviderBuilder() {
117 if (switchingProviderBuilders.size() % MAX_CASES_PER_CLASS == 0) {
bcorsoaf9e0042018-04-16 14:12:23 -0700118 String name = switchingProviderNames.getUniqueName("SwitchingProvider");
bcorsocc739fe2018-04-23 09:16:12 -0700119 SwitchingProviderBuilder switchingProviderBuilder =
120 new SwitchingProviderBuilder(owningComponent.nestedClass(name));
dpb159d81b2018-11-01 14:06:17 -0700121 componentImplementation.addSwitchingProvider(switchingProviderBuilder::build);
bcorsocc739fe2018-04-23 09:16:12 -0700122 return switchingProviderBuilder;
bcorsoaf9e0042018-04-16 14:12:23 -0700123 }
bcorsocc739fe2018-04-23 09:16:12 -0700124 return getLast(switchingProviderBuilders.values());
bcorsoaf9e0042018-04-16 14:12:23 -0700125 }
126
127 // TODO(user): Consider just merging this class with SwitchingProviders.
bcorsocc739fe2018-04-23 09:16:12 -0700128 private final class SwitchingProviderBuilder {
bcorsoaf9e0042018-04-16 14:12:23 -0700129 // Keep the switch cases ordered by switch id. The switch Ids are assigned in pre-order
130 // traversal, but the switch cases are assigned in post-order traversal of the binding graph.
131 private final Map<Integer, CodeBlock> switchCases = new TreeMap<>();
132 private final Map<Key, Integer> switchIds = new HashMap<>();
133 private final ClassName switchingProviderType;
134
bcorsocc739fe2018-04-23 09:16:12 -0700135 SwitchingProviderBuilder(ClassName switchingProviderType) {
bcorsoaf9e0042018-04-16 14:12:23 -0700136 this.switchingProviderType = checkNotNull(switchingProviderType);
137 }
138
bcorsocc739fe2018-04-23 09:16:12 -0700139 Expression getProviderExpression(SwitchCase switchCase) {
140 Key key = switchCase.key();
141 if (!switchIds.containsKey(key)) {
bcorsoaf9e0042018-04-16 14:12:23 -0700142 int switchId = switchIds.size();
bcorsocc739fe2018-04-23 09:16:12 -0700143 switchIds.put(key, switchId);
144 switchCases.put(switchId, createSwitchCaseCodeBlock(switchCase));
bcorsoaf9e0042018-04-16 14:12:23 -0700145 }
bcorsocc739fe2018-04-23 09:16:12 -0700146 return switchCase.getProviderExpression(switchingProviderType, switchIds.get(key));
bcorsoaf9e0042018-04-16 14:12:23 -0700147 }
148
bcorsocc739fe2018-04-23 09:16:12 -0700149 private CodeBlock createSwitchCaseCodeBlock(SwitchCase switchCase) {
bcorsobe9493d2018-10-04 13:52:31 -0700150 CodeBlock instanceCodeBlock =
151 switchCase.getReturnExpression(switchingProviderType).box(types).codeBlock();
bcorso8a8039a2018-03-19 09:36:26 -0700152
bcorsoaf9e0042018-04-16 14:12:23 -0700153 return CodeBlock.builder()
154 // TODO(user): Is there something else more useful than the key?
bcorsocc739fe2018-04-23 09:16:12 -0700155 .add("case $L: // $L \n", switchIds.get(switchCase.key()), switchCase.key())
bcorsoaf9e0042018-04-16 14:12:23 -0700156 .addStatement("return ($T) $L", T, instanceCodeBlock)
157 .build();
158 }
bcorso8a8039a2018-03-19 09:36:26 -0700159
bcorsocc739fe2018-04-23 09:16:12 -0700160 private TypeSpec build() {
161 return createSwitchingProviderType(
162 classBuilder(switchingProviderType)
163 .addTypeVariable(T)
164 .addSuperinterface(providerOf(T))
165 .addMethods(getMethods()));
bcorsoaf9e0042018-04-16 14:12:23 -0700166 }
167
168 private ImmutableList<MethodSpec> getMethods() {
169 ImmutableList<CodeBlock> switchCodeBlockPartitions = switchCodeBlockPartitions();
170 if (switchCodeBlockPartitions.size() == 1) {
171 // There are less than MAX_CASES_PER_SWITCH cases, so no need for extra get methods.
172 return ImmutableList.of(
bcorso8a8039a2018-03-19 09:36:26 -0700173 methodBuilder("get")
174 .addModifiers(PUBLIC)
175 .addAnnotation(suppressWarnings(UNCHECKED))
176 .addAnnotation(Override.class)
177 .returns(T)
bcorsoaf9e0042018-04-16 14:12:23 -0700178 .addCode(getOnlyElement(switchCodeBlockPartitions))
179 .build());
180 }
181
182 // This is the main public "get" method that will route to private getter methods.
183 MethodSpec.Builder routerMethod =
184 methodBuilder("get")
185 .addModifiers(PUBLIC)
186 .addAnnotation(Override.class)
187 .returns(T)
188 .beginControlFlow("switch (id / $L)", MAX_CASES_PER_SWITCH);
189
190 ImmutableList.Builder<MethodSpec> getMethods = ImmutableList.builder();
191 for (int i = 0; i < switchCodeBlockPartitions.size(); i++) {
192 MethodSpec method =
193 methodBuilder("get" + i)
194 .addModifiers(PRIVATE)
195 .addAnnotation(suppressWarnings(UNCHECKED))
196 .returns(T)
197 .addCode(switchCodeBlockPartitions.get(i))
198 .build();
199 getMethods.add(method);
200 routerMethod.addStatement("case $L: return $N()", i, method);
201 }
202
203 routerMethod.addStatement("default: throw new $T(id)", AssertionError.class).endControlFlow();
204
205 return getMethods.add(routerMethod.build()).build();
206 }
207
208 private ImmutableList<CodeBlock> switchCodeBlockPartitions() {
209 return Lists.partition(ImmutableList.copyOf(switchCases.values()), MAX_CASES_PER_SWITCH)
210 .stream()
211 .map(
212 partitionCases ->
213 CodeBlock.builder()
214 .beginControlFlow("switch (id)")
215 .add(CodeBlocks.concat(partitionCases))
216 .addStatement("default: throw new $T(id)", AssertionError.class)
217 .endControlFlow()
218 .build())
219 .collect(toImmutableList());
220 }
bcorso8a8039a2018-03-19 09:36:26 -0700221 }
bcorso8a8039a2018-03-19 09:36:26 -0700222}