1 /* 2 * Copyright (C) 2014 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.binding; 18 19 import static androidx.room.compiler.codegen.XTypeNameKt.toJavaPoet; 20 import static androidx.room.compiler.processing.XElementKt.isConstructor; 21 import static com.google.common.base.CaseFormat.LOWER_CAMEL; 22 import static com.google.common.base.CaseFormat.UPPER_CAMEL; 23 import static com.google.common.base.Preconditions.checkArgument; 24 import static com.google.common.base.Verify.verify; 25 import static dagger.internal.codegen.javapoet.TypeNames.DOUBLE_CHECK; 26 import static dagger.internal.codegen.javapoet.TypeNames.PRODUCER; 27 import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER; 28 import static dagger.internal.codegen.javapoet.TypeNames.PROVIDER_OF_LAZY; 29 import static dagger.internal.codegen.model.BindingKind.ASSISTED_INJECTION; 30 import static dagger.internal.codegen.model.BindingKind.INJECTION; 31 import static dagger.internal.codegen.xprocessing.XElements.asExecutable; 32 import static dagger.internal.codegen.xprocessing.XElements.asMethod; 33 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement; 34 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 35 import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames; 36 import static dagger.internal.codegen.xprocessing.XTypeNames.simpleName; 37 import static javax.lang.model.SourceVersion.isName; 38 39 import androidx.room.compiler.codegen.XClassName; 40 import androidx.room.compiler.processing.XExecutableElement; 41 import androidx.room.compiler.processing.XFieldElement; 42 import androidx.room.compiler.processing.XMethodElement; 43 import androidx.room.compiler.processing.XTypeElement; 44 import com.google.common.base.Joiner; 45 import com.google.common.collect.ImmutableList; 46 import com.google.common.collect.ImmutableMap; 47 import com.google.common.collect.ImmutableSet; 48 import com.google.common.collect.Iterables; 49 import com.google.common.collect.Maps; 50 import com.squareup.javapoet.ClassName; 51 import com.squareup.javapoet.CodeBlock; 52 import com.squareup.javapoet.FieldSpec; 53 import com.squareup.javapoet.ParameterizedTypeName; 54 import com.squareup.javapoet.TypeName; 55 import com.squareup.javapoet.TypeVariableName; 56 import dagger.internal.codegen.base.MapType; 57 import dagger.internal.codegen.base.SetType; 58 import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; 59 import dagger.internal.codegen.javapoet.TypeNames; 60 import dagger.internal.codegen.model.DependencyRequest; 61 import dagger.internal.codegen.model.RequestKind; 62 import dagger.internal.codegen.xprocessing.XTypeNames; 63 import javax.inject.Inject; 64 import javax.lang.model.SourceVersion; 65 66 /** Utilities for generating files. */ 67 public final class SourceFiles { 68 69 private static final Joiner CLASS_FILE_NAME_JOINER = Joiner.on('_'); 70 SourceFiles()71 @Inject SourceFiles() {} 72 73 /** 74 * Generates names and keys for the factory class fields needed to hold the framework classes for 75 * all of the dependencies of {@code binding}. It is responsible for choosing a name that 76 * 77 * <ul> 78 * <li>represents all of the dependency requests for this key 79 * <li>is <i>probably</i> associated with the type being bound 80 * <li>is unique within the class 81 * </ul> 82 * 83 * @param binding must be an unresolved binding (type parameters must match its type element's) 84 */ 85 public static ImmutableMap<DependencyRequest, FrameworkField> generateBindingFieldsForDependencies(Binding binding)86 generateBindingFieldsForDependencies(Binding binding) { 87 checkArgument(!binding.unresolved().isPresent(), "binding must be unresolved: %s", binding); 88 89 FrameworkTypeMapper frameworkTypeMapper = 90 FrameworkTypeMapper.forBindingType(binding.bindingType()); 91 92 return Maps.toMap( 93 binding.dependencies(), 94 dependency -> { 95 XClassName frameworkClassName = 96 frameworkTypeMapper.getFrameworkType(dependency.kind()).frameworkClassName(); 97 return FrameworkField.create( 98 DependencyVariableNamer.name(dependency), 99 frameworkClassName, 100 dependency.key().type().xprocessing()); 101 }); 102 } 103 frameworkTypeUsageStatement( CodeBlock frameworkTypeMemberSelect, RequestKind dependencyKind)104 public CodeBlock frameworkTypeUsageStatement( 105 CodeBlock frameworkTypeMemberSelect, RequestKind dependencyKind) { 106 switch (dependencyKind) { 107 case LAZY: 108 return CodeBlock.of( 109 "$T.lazy($L)", 110 DOUBLE_CHECK, 111 frameworkTypeMemberSelect); 112 case INSTANCE: 113 case FUTURE: 114 return CodeBlock.of("$L.get()", frameworkTypeMemberSelect); 115 case PROVIDER: 116 case PRODUCER: 117 return frameworkTypeMemberSelect; 118 case PROVIDER_OF_LAZY: 119 return CodeBlock.of("$T.create($L)", PROVIDER_OF_LAZY, frameworkTypeMemberSelect); 120 default: // including PRODUCED 121 throw new AssertionError(dependencyKind); 122 } 123 } 124 125 /** 126 * Returns a mapping of {@link DependencyRequest}s to {@link CodeBlock}s that {@linkplain 127 * #frameworkTypeUsageStatement(CodeBlock, RequestKind) use them}. 128 */ frameworkFieldUsages( ImmutableSet<DependencyRequest> dependencies, ImmutableMap<DependencyRequest, FieldSpec> fields)129 public ImmutableMap<DependencyRequest, CodeBlock> frameworkFieldUsages( 130 ImmutableSet<DependencyRequest> dependencies, 131 ImmutableMap<DependencyRequest, FieldSpec> fields) { 132 return Maps.toMap( 133 dependencies, 134 dep -> frameworkTypeUsageStatement(CodeBlock.of("$N", fields.get(dep)), dep.kind())); 135 } 136 generatedProxyMethodName(ContributionBinding binding)137 public static String generatedProxyMethodName(ContributionBinding binding) { 138 switch (binding.kind()) { 139 case INJECTION: 140 case ASSISTED_INJECTION: 141 return "newInstance"; 142 case PROVISION: 143 XMethodElement method = asMethod(binding.bindingElement().get()); 144 String simpleName = getSimpleName(method); 145 // If the simple name is already defined in the factory, prepend "proxy" to the name. 146 return simpleName.contentEquals("get") || simpleName.contentEquals("create") 147 ? "proxy" + LOWER_CAMEL.to(UPPER_CAMEL, simpleName) 148 : simpleName; 149 default: 150 throw new AssertionError("Unexpected binding kind: " + binding); 151 } 152 } 153 154 /** Returns the generated factory or members injector name for a binding. */ generatedClassNameForBinding(Binding binding)155 public static XClassName generatedClassNameForBinding(Binding binding) { 156 switch (binding.kind()) { 157 case ASSISTED_INJECTION: 158 case INJECTION: 159 case PROVISION: 160 case PRODUCTION: 161 return factoryNameForElement(asExecutable(binding.bindingElement().get())); 162 case ASSISTED_FACTORY: 163 return siblingClassName(asTypeElement(binding.bindingElement().get()), "_Impl"); 164 case MEMBERS_INJECTION: 165 return membersInjectorNameForType( 166 ((MembersInjectionBinding) binding).membersInjectedType()); 167 default: 168 throw new AssertionError(); 169 } 170 } 171 172 /** 173 * Returns the generated factory name for the given element. 174 * 175 * <p>This method is useful during validation before a {@link Binding} can be created. If a 176 * binding already exists for the given element, prefer to call {@link 177 * #generatedClassNameForBinding(Binding)} instead since this method does not validate that the 178 * given element is actually a binding element or not. 179 */ factoryNameForElement(XExecutableElement element)180 public static XClassName factoryNameForElement(XExecutableElement element) { 181 return elementBasedClassName(element, "Factory"); 182 } 183 184 /** 185 * Calculates an appropriate {@link XClassName} for a generated class that is based on {@code 186 * element}, appending {@code suffix} at the end. 187 * 188 * <p>This will always return a top level class name, even if {@code element}'s enclosing class is 189 * a nested type. 190 */ elementBasedClassName(XExecutableElement element, String suffix)191 public static XClassName elementBasedClassName(XExecutableElement element, String suffix) { 192 XClassName enclosingClassName = element.getEnclosingElement().asClassName(); 193 String methodName = 194 isConstructor(element) ? "" : LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(element)); 195 return XClassName.Companion.get( 196 enclosingClassName.getPackageName(), 197 classFileName(enclosingClassName) + "_" + methodName + suffix); 198 } 199 parameterizedGeneratedTypeNameForBinding(Binding binding)200 public static TypeName parameterizedGeneratedTypeNameForBinding(Binding binding) { 201 ClassName className = toJavaPoet(generatedClassNameForBinding(binding)); 202 ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding); 203 return typeParameters.isEmpty() 204 ? className 205 : ParameterizedTypeName.get(className, Iterables.toArray(typeParameters, TypeName.class)); 206 } 207 membersInjectorNameForType(XTypeElement typeElement)208 public static XClassName membersInjectorNameForType(XTypeElement typeElement) { 209 return siblingClassName(typeElement, "_MembersInjector"); 210 } 211 memberInjectedFieldSignatureForVariable(XFieldElement field)212 public static String memberInjectedFieldSignatureForVariable(XFieldElement field) { 213 return field.getEnclosingElement().getClassName().canonicalName() + "." + getSimpleName(field); 214 } 215 216 /* 217 * TODO(ronshapiro): this isn't perfect, as collisions could still exist. Some examples: 218 * 219 * - @Inject void members() {} will generate a method that conflicts with the instance 220 * method `injectMembers(T)` 221 * - Adding the index could conflict with another member: 222 * @Inject void a(Object o) {} 223 * @Inject void a(String s) {} 224 * @Inject void a1(String s) {} 225 * 226 * Here, Method a(String) will add the suffix "1", which will conflict with the method 227 * generated for a1(String) 228 * - Members named "members" or "methods" could also conflict with the {@code static} injection 229 * method. 230 */ membersInjectorMethodName(InjectionSite injectionSite)231 public static String membersInjectorMethodName(InjectionSite injectionSite) { 232 int index = injectionSite.indexAmongAtInjectMembersWithSameSimpleName(); 233 String indexString = index == 0 ? "" : String.valueOf(index + 1); 234 return "inject" 235 + LOWER_CAMEL.to(UPPER_CAMEL, getSimpleName(injectionSite.element())) 236 + indexString; 237 } 238 classFileName(XClassName className)239 public static String classFileName(XClassName className) { 240 return CLASS_FILE_NAME_JOINER.join(className.getSimpleNames()); 241 } 242 generatedMonitoringModuleName(XTypeElement componentElement)243 public static XClassName generatedMonitoringModuleName(XTypeElement componentElement) { 244 return siblingClassName(componentElement, "_MonitoringModule"); 245 } 246 247 // TODO(ronshapiro): when JavaPoet migration is complete, replace the duplicated code 248 // which could use this. siblingClassName(XTypeElement typeElement, String suffix)249 private static XClassName siblingClassName(XTypeElement typeElement, String suffix) { 250 XClassName className = typeElement.asClassName(); 251 return XClassName.Companion.get(className.getPackageName(), classFileName(className) + suffix); 252 } 253 254 /** 255 * The {@link java.util.Set} factory class name appropriate for set bindings. 256 * 257 * <ul> 258 * <li>{@link dagger.producers.internal.SetFactory} for provision bindings. 259 * <li>{@link dagger.producers.internal.SetProducer} for production bindings for {@code Set<T>}. 260 * <li>{@link dagger.producers.internal.SetOfProducedProducer} for production bindings for 261 * {@code Set<Produced<T>>}. 262 * </ul> 263 */ setFactoryClassName(MultiboundSetBinding binding)264 public static XClassName setFactoryClassName(MultiboundSetBinding binding) { 265 switch (binding.bindingType()) { 266 case PROVISION: 267 return XTypeNames.SET_FACTORY; 268 case PRODUCTION: 269 SetType setType = SetType.from(binding.key()); 270 return setType.elementsAreTypeOf(TypeNames.PRODUCED) 271 ? XTypeNames.SET_OF_PRODUCED_PRODUCER 272 : XTypeNames.SET_PRODUCER; 273 default: 274 throw new IllegalArgumentException(binding.bindingType().toString()); 275 } 276 } 277 278 /** The {@link java.util.Map} factory class name appropriate for map bindings. */ mapFactoryClassName(MultiboundMapBinding binding)279 public static XClassName mapFactoryClassName(MultiboundMapBinding binding) { 280 MapType mapType = MapType.from(binding.key()); 281 switch (binding.bindingType()) { 282 case PROVISION: 283 return mapType.valuesAreTypeOf(PROVIDER) 284 ? XTypeNames.MAP_PROVIDER_FACTORY : XTypeNames.MAP_FACTORY; 285 case PRODUCTION: 286 return mapType.valuesAreFrameworkType() 287 ? mapType.valuesAreTypeOf(PRODUCER) 288 ? XTypeNames.MAP_OF_PRODUCER_PRODUCER 289 : XTypeNames.MAP_OF_PRODUCED_PRODUCER 290 : XTypeNames.MAP_PRODUCER; 291 default: 292 throw new IllegalArgumentException(binding.bindingType().toString()); 293 } 294 } 295 bindingTypeElementTypeVariableNames( Binding binding)296 public static ImmutableList<TypeVariableName> bindingTypeElementTypeVariableNames( 297 Binding binding) { 298 if (binding instanceof ContributionBinding) { 299 ContributionBinding contributionBinding = (ContributionBinding) binding; 300 if (!(contributionBinding.kind() == INJECTION 301 || contributionBinding.kind() == ASSISTED_INJECTION) 302 && !contributionBinding.requiresModuleInstance()) { 303 return ImmutableList.of(); 304 } 305 } 306 return typeVariableNames(binding.bindingTypeElement().get()); 307 } 308 309 /** 310 * Returns a name to be used for variables of the given {@linkplain XTypeElement type}. Prefer 311 * semantically meaningful variable names, but if none can be derived, this will produce something 312 * readable. 313 */ 314 // TODO(gak): maybe this should be a function of TypeMirrors instead of Elements? simpleVariableName(XTypeElement typeElement)315 public static String simpleVariableName(XTypeElement typeElement) { 316 return simpleVariableName(typeElement.asClassName()); 317 } 318 319 /** 320 * Returns a name to be used for variables of the given {@link XClassName}. Prefer semantically 321 * meaningful variable names, but if none can be derived, this will produce something readable. 322 */ simpleVariableName(XClassName className)323 public static String simpleVariableName(XClassName className) { 324 String candidateName = UPPER_CAMEL.to(LOWER_CAMEL, simpleName(className)); 325 String variableName = protectAgainstKeywords(candidateName); 326 verify(isName(variableName), "'%s' was expected to be a valid variable name", variableName); 327 return variableName; 328 } 329 protectAgainstKeywords(String candidateName)330 public static String protectAgainstKeywords(String candidateName) { 331 switch (candidateName) { 332 case "package": 333 return "pkg"; 334 case "boolean": 335 return "b"; 336 case "double": 337 return "d"; 338 case "byte": 339 return "b"; 340 case "int": 341 return "i"; 342 case "short": 343 return "s"; 344 case "char": 345 return "c"; 346 case "void": 347 return "v"; 348 case "class": 349 return "clazz"; 350 case "float": 351 return "f"; 352 case "long": 353 return "l"; 354 default: 355 return SourceVersion.isKeyword(candidateName) ? candidateName + '_' : candidateName; 356 } 357 } 358 } 359