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 com.google.common.base.CaseFormat.LOWER_CAMEL; 20 import static com.google.common.base.CaseFormat.UPPER_CAMEL; 21 import static com.google.common.base.Preconditions.checkArgument; 22 import static com.google.common.base.Preconditions.checkState; 23 import static com.google.common.base.Verify.verify; 24 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 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.PROVIDER_OF_LAZY; 32 import static dagger.internal.codegen.javapoet.TypeNames.SET_FACTORY; 33 import static dagger.internal.codegen.javapoet.TypeNames.SET_OF_PRODUCED_PRODUCER; 34 import static dagger.internal.codegen.javapoet.TypeNames.SET_PRODUCER; 35 import static dagger.model.BindingKind.ASSISTED_INJECTION; 36 import static dagger.model.BindingKind.INJECTION; 37 import static dagger.model.BindingKind.MULTIBOUND_MAP; 38 import static dagger.model.BindingKind.MULTIBOUND_SET; 39 import static javax.lang.model.SourceVersion.isName; 40 41 import com.google.auto.common.MoreElements; 42 import com.google.common.base.Joiner; 43 import com.google.common.collect.ImmutableList; 44 import com.google.common.collect.ImmutableMap; 45 import com.google.common.collect.ImmutableSet; 46 import com.google.common.collect.Iterables; 47 import com.google.common.collect.Maps; 48 import com.squareup.javapoet.ClassName; 49 import com.squareup.javapoet.CodeBlock; 50 import com.squareup.javapoet.FieldSpec; 51 import com.squareup.javapoet.ParameterizedTypeName; 52 import com.squareup.javapoet.TypeName; 53 import com.squareup.javapoet.TypeVariableName; 54 import dagger.internal.SetFactory; 55 import dagger.internal.codegen.base.MapType; 56 import dagger.internal.codegen.base.SetType; 57 import dagger.model.DependencyRequest; 58 import dagger.model.RequestKind; 59 import dagger.producers.Produced; 60 import dagger.producers.Producer; 61 import dagger.producers.internal.SetOfProducedProducer; 62 import dagger.producers.internal.SetProducer; 63 import java.util.List; 64 import javax.inject.Provider; 65 import javax.lang.model.SourceVersion; 66 import javax.lang.model.element.ElementKind; 67 import javax.lang.model.element.ExecutableElement; 68 import javax.lang.model.element.TypeElement; 69 import javax.lang.model.element.TypeParameterElement; 70 import javax.lang.model.element.VariableElement; 71 72 /** Utilities for generating files. */ 73 public class SourceFiles { 74 75 private static final Joiner CLASS_FILE_NAME_JOINER = Joiner.on('_'); 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 FrameworkField.create( 100 ClassName.get( 101 frameworkTypeMapper.getFrameworkType(dependency.kind()).frameworkClass()), 102 TypeName.get(dependency.key().type()), 103 DependencyVariableNamer.name(dependency))); 104 } 105 frameworkTypeUsageStatement( CodeBlock frameworkTypeMemberSelect, RequestKind dependencyKind)106 public static CodeBlock frameworkTypeUsageStatement( 107 CodeBlock frameworkTypeMemberSelect, RequestKind dependencyKind) { 108 switch (dependencyKind) { 109 case LAZY: 110 return CodeBlock.of("$T.lazy($L)", DOUBLE_CHECK, frameworkTypeMemberSelect); 111 case INSTANCE: 112 case FUTURE: 113 return CodeBlock.of("$L.get()", frameworkTypeMemberSelect); 114 case PROVIDER: 115 case PRODUCER: 116 return frameworkTypeMemberSelect; 117 case PROVIDER_OF_LAZY: 118 return CodeBlock.of("$T.create($L)", PROVIDER_OF_LAZY, frameworkTypeMemberSelect); 119 default: // including PRODUCED 120 throw new AssertionError(dependencyKind); 121 } 122 } 123 124 /** 125 * Returns a mapping of {@link DependencyRequest}s to {@link CodeBlock}s that {@linkplain 126 * #frameworkTypeUsageStatement(CodeBlock, RequestKind) use them}. 127 */ frameworkFieldUsages( ImmutableSet<DependencyRequest> dependencies, ImmutableMap<DependencyRequest, FieldSpec> fields)128 public static ImmutableMap<DependencyRequest, CodeBlock> frameworkFieldUsages( 129 ImmutableSet<DependencyRequest> dependencies, 130 ImmutableMap<DependencyRequest, FieldSpec> fields) { 131 return Maps.toMap( 132 dependencies, 133 dep -> frameworkTypeUsageStatement(CodeBlock.of("$N", fields.get(dep)), dep.kind())); 134 } 135 136 /** Returns the generated factory or members injector name for a binding. */ generatedClassNameForBinding(Binding binding)137 public static ClassName generatedClassNameForBinding(Binding binding) { 138 switch (binding.bindingType()) { 139 case PROVISION: 140 case PRODUCTION: 141 ContributionBinding contribution = (ContributionBinding) binding; 142 switch (contribution.kind()) { 143 case ASSISTED_INJECTION: 144 case INJECTION: 145 case PROVISION: 146 case PRODUCTION: 147 return elementBasedClassName( 148 MoreElements.asExecutable(binding.bindingElement().get()), "Factory"); 149 150 case ASSISTED_FACTORY: 151 return siblingClassName(MoreElements.asType(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 * Calculates an appropriate {@link ClassName} for a generated class that is based on {@code 166 * element}, appending {@code suffix} at the end. 167 * 168 * <p>This will always return a {@linkplain ClassName#topLevelClassName() top level class name}, 169 * even if {@code element}'s enclosing class is a nested type. 170 */ elementBasedClassName(ExecutableElement element, String suffix)171 public static ClassName elementBasedClassName(ExecutableElement element, String suffix) { 172 ClassName enclosingClassName = 173 ClassName.get(MoreElements.asType(element.getEnclosingElement())); 174 String methodName = 175 element.getKind().equals(ElementKind.CONSTRUCTOR) 176 ? "" 177 : LOWER_CAMEL.to(UPPER_CAMEL, element.getSimpleName().toString()); 178 return ClassName.get( 179 enclosingClassName.packageName(), 180 classFileName(enclosingClassName) + "_" + methodName + suffix); 181 } 182 parameterizedGeneratedTypeNameForBinding(Binding binding)183 public static TypeName parameterizedGeneratedTypeNameForBinding(Binding binding) { 184 ClassName className = generatedClassNameForBinding(binding); 185 ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding); 186 return typeParameters.isEmpty() 187 ? className 188 : ParameterizedTypeName.get(className, Iterables.toArray(typeParameters, TypeName.class)); 189 } 190 membersInjectorNameForType(TypeElement typeElement)191 public static ClassName membersInjectorNameForType(TypeElement typeElement) { 192 return siblingClassName(typeElement, "_MembersInjector"); 193 } 194 memberInjectedFieldSignatureForVariable(VariableElement variableElement)195 public static String memberInjectedFieldSignatureForVariable(VariableElement variableElement) { 196 return MoreElements.asType(variableElement.getEnclosingElement()).getQualifiedName() 197 + "." 198 + variableElement.getSimpleName(); 199 } 200 classFileName(ClassName className)201 public static String classFileName(ClassName className) { 202 return CLASS_FILE_NAME_JOINER.join(className.simpleNames()); 203 } 204 generatedMonitoringModuleName(TypeElement componentElement)205 public static ClassName generatedMonitoringModuleName(TypeElement componentElement) { 206 return siblingClassName(componentElement, "_MonitoringModule"); 207 } 208 209 // TODO(ronshapiro): when JavaPoet migration is complete, replace the duplicated code 210 // which could use this. siblingClassName(TypeElement typeElement, String suffix)211 private static ClassName siblingClassName(TypeElement typeElement, String suffix) { 212 ClassName className = ClassName.get(typeElement); 213 return className.topLevelClassName().peerClass(classFileName(className) + suffix); 214 } 215 216 /** 217 * The {@link java.util.Set} factory class name appropriate for set bindings. 218 * 219 * <ul> 220 * <li>{@link SetFactory} for provision bindings. 221 * <li>{@link SetProducer} for production bindings for {@code Set<T>}. 222 * <li>{@link SetOfProducedProducer} for production bindings for {@code Set<Produced<T>>}. 223 * </ul> 224 */ setFactoryClassName(ContributionBinding binding)225 public static ClassName setFactoryClassName(ContributionBinding binding) { 226 checkArgument(binding.kind().equals(MULTIBOUND_SET)); 227 if (binding.bindingType().equals(BindingType.PROVISION)) { 228 return SET_FACTORY; 229 } else { 230 SetType setType = SetType.from(binding.key()); 231 return setType.elementsAreTypeOf(Produced.class) ? SET_OF_PRODUCED_PRODUCER : SET_PRODUCER; 232 } 233 } 234 235 /** The {@link java.util.Map} factory class name appropriate for map bindings. */ mapFactoryClassName(ContributionBinding binding)236 public static ClassName mapFactoryClassName(ContributionBinding binding) { 237 checkState(binding.kind().equals(MULTIBOUND_MAP), binding.kind()); 238 MapType mapType = MapType.from(binding.key()); 239 switch (binding.bindingType()) { 240 case PROVISION: 241 return mapType.valuesAreTypeOf(Provider.class) ? MAP_PROVIDER_FACTORY : MAP_FACTORY; 242 case PRODUCTION: 243 return mapType.valuesAreFrameworkType() 244 ? mapType.valuesAreTypeOf(Producer.class) 245 ? MAP_OF_PRODUCER_PRODUCER 246 : MAP_OF_PRODUCED_PRODUCER 247 : MAP_PRODUCER; 248 default: 249 throw new IllegalArgumentException(binding.bindingType().toString()); 250 } 251 } 252 bindingTypeElementTypeVariableNames( Binding binding)253 public static ImmutableList<TypeVariableName> bindingTypeElementTypeVariableNames( 254 Binding binding) { 255 if (binding instanceof ContributionBinding) { 256 ContributionBinding contributionBinding = (ContributionBinding) binding; 257 if (!(contributionBinding.kind() == INJECTION 258 || contributionBinding.kind() == ASSISTED_INJECTION) 259 && !contributionBinding.requiresModuleInstance()) { 260 return ImmutableList.of(); 261 } 262 } 263 List<? extends TypeParameterElement> typeParameters = 264 binding.bindingTypeElement().get().getTypeParameters(); 265 return typeParameters.stream().map(TypeVariableName::get).collect(toImmutableList()); 266 } 267 268 /** 269 * Returns a name to be used for variables of the given {@linkplain TypeElement type}. Prefer 270 * semantically meaningful variable names, but if none can be derived, this will produce something 271 * readable. 272 */ 273 // TODO(gak): maybe this should be a function of TypeMirrors instead of Elements? simpleVariableName(TypeElement typeElement)274 public static String simpleVariableName(TypeElement typeElement) { 275 String candidateName = UPPER_CAMEL.to(LOWER_CAMEL, typeElement.getSimpleName().toString()); 276 String variableName = protectAgainstKeywords(candidateName); 277 verify(isName(variableName), "'%s' was expected to be a valid variable name"); 278 return variableName; 279 } 280 protectAgainstKeywords(String candidateName)281 public static String protectAgainstKeywords(String candidateName) { 282 switch (candidateName) { 283 case "package": 284 return "pkg"; 285 case "boolean": 286 return "b"; 287 case "double": 288 return "d"; 289 case "byte": 290 return "b"; 291 case "int": 292 return "i"; 293 case "short": 294 return "s"; 295 case "char": 296 return "c"; 297 case "void": 298 return "v"; 299 case "class": 300 return "clazz"; 301 case "float": 302 return "f"; 303 case "long": 304 return "l"; 305 default: 306 return SourceVersion.isKeyword(candidateName) ? candidateName + '_' : candidateName; 307 } 308 } 309 SourceFiles()310 private SourceFiles() {} 311 } 312