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