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