• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package dagger.internal.codegen;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static com.google.common.collect.Iterables.getLast;
21 import static com.google.common.collect.Iterables.getOnlyElement;
22 import static com.squareup.javapoet.MethodSpec.methodBuilder;
23 import static com.squareup.javapoet.TypeSpec.classBuilder;
24 import static dagger.internal.codegen.DaggerStreams.toImmutableList;
25 import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED;
26 import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings;
27 import static dagger.internal.codegen.javapoet.TypeNames.providerOf;
28 import static javax.lang.model.element.Modifier.PRIVATE;
29 import static javax.lang.model.element.Modifier.PUBLIC;
30 
31 import com.google.common.collect.ImmutableList;
32 import com.google.common.collect.Lists;
33 import com.squareup.javapoet.ClassName;
34 import com.squareup.javapoet.CodeBlock;
35 import com.squareup.javapoet.MethodSpec;
36 import com.squareup.javapoet.TypeSpec;
37 import com.squareup.javapoet.TypeVariableName;
38 import dagger.internal.codegen.javapoet.CodeBlocks;
39 import dagger.internal.codegen.javapoet.Expression;
40 import dagger.internal.codegen.langmodel.DaggerTypes;
41 import dagger.model.Key;
42 import java.util.HashMap;
43 import java.util.LinkedHashMap;
44 import java.util.Map;
45 import java.util.TreeMap;
46 
47 /**
48  * Keeps track of all provider expression requests for a component.
49  *
50  * <p>The provider expression request will be satisfied by a single generated {@code Provider} inner
51  * class that can provide instances for all types by switching on an id.
52  */
53 // TODO(ronshapiro): either merge this with InnerSwitchingProviders, or repurpose this for
54 // SwitchingProducers
55 abstract class SwitchingProviders {
56   /**
57    * Defines the {@linkplain Expression expressions} for a switch case in a {@code SwitchProvider}
58    * for a particular binding.
59    */
60   // TODO(user): Consider handling SwitchingProviders with dependency arguments in this class,
61   // then we wouldn't need the getProviderExpression method.
62   // TODO(user): Consider making this an abstract class with equals/hashCode defined by the key
63   // and then using this class directly in Map types instead of Key.
64   interface SwitchCase {
65     /** Returns the {@link Key} for this switch case. */
key()66     Key key();
67 
68     /** Returns the {@link Expression} that returns the provided instance for this case. */
getReturnExpression(ClassName switchingProviderClass)69     Expression getReturnExpression(ClassName switchingProviderClass);
70 
71     /**
72      * Returns the {@link Expression} that returns the {@code SwitchProvider} instance for this
73      * case.
74      */
getProviderExpression(ClassName switchingProviderClass, int switchId)75     Expression getProviderExpression(ClassName switchingProviderClass, int switchId);
76   }
77 
78   /**
79    * Each switch size is fixed at 100 cases each and put in its own method. This is to limit the
80    * size of the methods so that we don't reach the "huge" method size limit for Android that will
81    * prevent it from being AOT compiled in some versions of Android (b/77652521). This generally
82    * starts to happen around 1500 cases, but we are choosing 100 to be safe.
83    */
84   // TODO(user): Include a proguard_spec in the Dagger library to prevent inlining these methods?
85   // TODO(ronshapiro): Consider making this configurable via a flag.
86   private static final int MAX_CASES_PER_SWITCH = 100;
87 
88   private static final long MAX_CASES_PER_CLASS = MAX_CASES_PER_SWITCH * MAX_CASES_PER_SWITCH;
89   private static final TypeVariableName T = TypeVariableName.get("T");
90 
91   /**
92    * Maps a {@link Key} to an instance of a {@link SwitchingProviderBuilder}. Each group of {@code
93    * MAX_CASES_PER_CLASS} keys will share the same instance.
94    */
95   private final Map<Key, SwitchingProviderBuilder> switchingProviderBuilders =
96       new LinkedHashMap<>();
97 
98   private final ComponentImplementation componentImplementation;
99   private final ClassName owningComponent;
100   private final DaggerTypes types;
101   private final UniqueNameSet switchingProviderNames = new UniqueNameSet();
102 
SwitchingProviders(ComponentImplementation componentImplementation, DaggerTypes types)103   SwitchingProviders(ComponentImplementation componentImplementation, DaggerTypes types) {
104     this.componentImplementation = checkNotNull(componentImplementation);
105     this.types = checkNotNull(types);
106     this.owningComponent = checkNotNull(componentImplementation).name();
107   }
108 
109   /** Returns the {@link TypeSpec} for a {@code SwitchingProvider} based on the given builder. */
createSwitchingProviderType(TypeSpec.Builder builder)110   protected abstract TypeSpec createSwitchingProviderType(TypeSpec.Builder builder);
111 
112   /**
113    * Returns the {@link Expression} that returns the {@code SwitchProvider} instance for the case.
114    */
getProviderExpression(SwitchCase switchCase)115   protected final Expression getProviderExpression(SwitchCase switchCase) {
116     return switchingProviderBuilders
117         .computeIfAbsent(switchCase.key(), key -> getSwitchingProviderBuilder())
118         .getProviderExpression(switchCase);
119   }
120 
getSwitchingProviderBuilder()121   private SwitchingProviderBuilder getSwitchingProviderBuilder() {
122     if (switchingProviderBuilders.size() % MAX_CASES_PER_CLASS == 0) {
123       String name = switchingProviderNames.getUniqueName("SwitchingProvider");
124       SwitchingProviderBuilder switchingProviderBuilder =
125           new SwitchingProviderBuilder(owningComponent.nestedClass(name));
126       componentImplementation.addSwitchingProvider(switchingProviderBuilder::build);
127       return switchingProviderBuilder;
128     }
129     return getLast(switchingProviderBuilders.values());
130   }
131 
132   // TODO(user): Consider just merging this class with SwitchingProviders.
133   private final class SwitchingProviderBuilder {
134     // Keep the switch cases ordered by switch id. The switch Ids are assigned in pre-order
135     // traversal, but the switch cases are assigned in post-order traversal of the binding graph.
136     private final Map<Integer, CodeBlock> switchCases = new TreeMap<>();
137     private final Map<Key, Integer> switchIds = new HashMap<>();
138     private final ClassName switchingProviderType;
139 
SwitchingProviderBuilder(ClassName switchingProviderType)140     SwitchingProviderBuilder(ClassName switchingProviderType) {
141       this.switchingProviderType = checkNotNull(switchingProviderType);
142     }
143 
getProviderExpression(SwitchCase switchCase)144     Expression getProviderExpression(SwitchCase switchCase) {
145       Key key = switchCase.key();
146       if (!switchIds.containsKey(key)) {
147         int switchId = switchIds.size();
148         switchIds.put(key, switchId);
149         switchCases.put(switchId, createSwitchCaseCodeBlock(switchCase));
150       }
151       return switchCase.getProviderExpression(switchingProviderType, switchIds.get(key));
152     }
153 
createSwitchCaseCodeBlock(SwitchCase switchCase)154     private CodeBlock createSwitchCaseCodeBlock(SwitchCase switchCase) {
155       CodeBlock instanceCodeBlock =
156           switchCase.getReturnExpression(switchingProviderType).box(types).codeBlock();
157 
158       return CodeBlock.builder()
159           // TODO(user): Is there something else more useful than the key?
160           .add("case $L: // $L \n", switchIds.get(switchCase.key()), switchCase.key())
161           .addStatement("return ($T) $L", T, instanceCodeBlock)
162           .build();
163     }
164 
build()165     private TypeSpec build() {
166       return createSwitchingProviderType(
167           classBuilder(switchingProviderType)
168               .addTypeVariable(T)
169               .addSuperinterface(providerOf(T))
170               .addMethods(getMethods()));
171     }
172 
getMethods()173     private ImmutableList<MethodSpec> getMethods() {
174       ImmutableList<CodeBlock> switchCodeBlockPartitions = switchCodeBlockPartitions();
175       if (switchCodeBlockPartitions.size() == 1) {
176         // There are less than MAX_CASES_PER_SWITCH cases, so no need for extra get methods.
177         return ImmutableList.of(
178             methodBuilder("get")
179                 .addModifiers(PUBLIC)
180                 .addAnnotation(suppressWarnings(UNCHECKED))
181                 .addAnnotation(Override.class)
182                 .returns(T)
183                 .addCode(getOnlyElement(switchCodeBlockPartitions))
184                 .build());
185       }
186 
187       // This is the main public "get" method that will route to private getter methods.
188       MethodSpec.Builder routerMethod =
189           methodBuilder("get")
190               .addModifiers(PUBLIC)
191               .addAnnotation(Override.class)
192               .returns(T)
193               .beginControlFlow("switch (id / $L)", MAX_CASES_PER_SWITCH);
194 
195       ImmutableList.Builder<MethodSpec> getMethods = ImmutableList.builder();
196       for (int i = 0; i < switchCodeBlockPartitions.size(); i++) {
197         MethodSpec method =
198             methodBuilder("get" + i)
199                 .addModifiers(PRIVATE)
200                 .addAnnotation(suppressWarnings(UNCHECKED))
201                 .returns(T)
202                 .addCode(switchCodeBlockPartitions.get(i))
203                 .build();
204         getMethods.add(method);
205         routerMethod.addStatement("case $L: return $N()", i, method);
206       }
207 
208       routerMethod.addStatement("default: throw new $T(id)", AssertionError.class).endControlFlow();
209 
210       return getMethods.add(routerMethod.build()).build();
211     }
212 
switchCodeBlockPartitions()213     private ImmutableList<CodeBlock> switchCodeBlockPartitions() {
214       return Lists.partition(ImmutableList.copyOf(switchCases.values()), MAX_CASES_PER_SWITCH)
215           .stream()
216           .map(
217               partitionCases ->
218                   CodeBlock.builder()
219                       .beginControlFlow("switch (id)")
220                       .add(CodeBlocks.concat(partitionCases))
221                       .addStatement("default: throw new $T(id)", AssertionError.class)
222                       .endControlFlow()
223                       .build())
224           .collect(toImmutableList());
225     }
226   }
227 }
228