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