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; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.collect.Maps.transformValues; 21 import static com.squareup.javapoet.MethodSpec.constructorBuilder; 22 import static com.squareup.javapoet.MethodSpec.methodBuilder; 23 import static com.squareup.javapoet.TypeSpec.classBuilder; 24 import static dagger.internal.codegen.ContributionBinding.FactoryCreationStrategy.DELEGATE; 25 import static dagger.internal.codegen.ContributionBinding.FactoryCreationStrategy.SINGLETON_INSTANCE; 26 import static dagger.internal.codegen.GwtCompatibility.gwtIncompatibleAnnotation; 27 import static dagger.internal.codegen.SourceFiles.bindingTypeElementTypeVariableNames; 28 import static dagger.internal.codegen.SourceFiles.frameworkFieldUsages; 29 import static dagger.internal.codegen.SourceFiles.frameworkTypeUsageStatement; 30 import static dagger.internal.codegen.SourceFiles.generateBindingFieldsForDependencies; 31 import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding; 32 import static dagger.internal.codegen.SourceFiles.parameterizedGeneratedTypeNameForBinding; 33 import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES; 34 import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; 35 import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings; 36 import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock; 37 import static dagger.internal.codegen.javapoet.TypeNames.factoryOf; 38 import static dagger.model.BindingKind.PROVISION; 39 import static javax.lang.model.element.Modifier.FINAL; 40 import static javax.lang.model.element.Modifier.PRIVATE; 41 import static javax.lang.model.element.Modifier.PUBLIC; 42 import static javax.lang.model.element.Modifier.STATIC; 43 44 import com.google.common.collect.ImmutableList; 45 import com.google.common.collect.ImmutableMap; 46 import com.google.common.collect.Lists; 47 import com.squareup.javapoet.ClassName; 48 import com.squareup.javapoet.CodeBlock; 49 import com.squareup.javapoet.FieldSpec; 50 import com.squareup.javapoet.MethodSpec; 51 import com.squareup.javapoet.ParameterSpec; 52 import com.squareup.javapoet.TypeName; 53 import com.squareup.javapoet.TypeSpec; 54 import dagger.internal.Factory; 55 import dagger.internal.Preconditions; 56 import dagger.internal.codegen.InjectionMethods.InjectionSiteMethod; 57 import dagger.internal.codegen.InjectionMethods.ProvisionMethod; 58 import dagger.internal.codegen.javapoet.CodeBlocks; 59 import dagger.internal.codegen.langmodel.DaggerElements; 60 import dagger.internal.codegen.langmodel.DaggerTypes; 61 import dagger.model.Key; 62 import java.util.List; 63 import java.util.Optional; 64 import javax.annotation.processing.Filer; 65 import javax.inject.Inject; 66 import javax.lang.model.SourceVersion; 67 import javax.lang.model.element.Element; 68 69 /** 70 * Generates {@link Factory} implementations from {@link ProvisionBinding} instances for 71 * {@link Inject} constructors. 72 */ 73 final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding> { 74 private final DaggerTypes types; 75 private final DaggerElements elements; 76 private final CompilerOptions compilerOptions; 77 78 @Inject FactoryGenerator( Filer filer, SourceVersion sourceVersion, DaggerTypes types, DaggerElements elements, CompilerOptions compilerOptions)79 FactoryGenerator( 80 Filer filer, 81 SourceVersion sourceVersion, 82 DaggerTypes types, 83 DaggerElements elements, 84 CompilerOptions compilerOptions) { 85 super(filer, elements, sourceVersion); 86 this.types = types; 87 this.elements = elements; 88 this.compilerOptions = compilerOptions; 89 } 90 91 @Override nameGeneratedType(ProvisionBinding binding)92 ClassName nameGeneratedType(ProvisionBinding binding) { 93 return generatedClassNameForBinding(binding); 94 } 95 96 @Override originatingElement(ProvisionBinding binding)97 Element originatingElement(ProvisionBinding binding) { 98 // we only create factories for bindings that have a binding element 99 return binding.bindingElement().get(); 100 } 101 102 @Override write(ClassName generatedTypeName, ProvisionBinding binding)103 Optional<TypeSpec.Builder> write(ClassName generatedTypeName, ProvisionBinding binding) { 104 // We don't want to write out resolved bindings -- we want to write out the generic version. 105 checkArgument(!binding.unresolved().isPresent()); 106 checkArgument(binding.bindingElement().isPresent()); 107 108 return binding.factoryCreationStrategy().equals(DELEGATE) 109 ? Optional.empty() 110 : Optional.of(factoryBuilder(binding)); 111 } 112 factoryBuilder(ProvisionBinding binding)113 private TypeSpec.Builder factoryBuilder(ProvisionBinding binding) { 114 TypeSpec.Builder factoryBuilder = 115 classBuilder(nameGeneratedType(binding)) 116 .addModifiers(PUBLIC, FINAL) 117 .addSuperinterface(factoryTypeName(binding)) 118 .addTypeVariables(bindingTypeElementTypeVariableNames(binding)); 119 120 addConstructorAndFields(binding, factoryBuilder); 121 factoryBuilder.addMethod(getMethod(binding)); 122 addCreateMethod(binding, factoryBuilder); 123 124 factoryBuilder.addMethod( 125 ProvisionMethod.create(binding, compilerOptions, elements).toMethodSpec()); 126 gwtIncompatibleAnnotation(binding).ifPresent(factoryBuilder::addAnnotation); 127 128 return factoryBuilder; 129 } 130 addConstructorAndFields(ProvisionBinding binding, TypeSpec.Builder factoryBuilder)131 private void addConstructorAndFields(ProvisionBinding binding, TypeSpec.Builder factoryBuilder) { 132 if (binding.factoryCreationStrategy().equals(SINGLETON_INSTANCE)) { 133 return; 134 } 135 // TODO(user): Make the constructor private? 136 MethodSpec.Builder constructor = constructorBuilder().addModifiers(PUBLIC); 137 constructorParams(binding).forEach( 138 param -> { 139 constructor.addParameter(param).addStatement("this.$1N = $1N", param); 140 factoryBuilder.addField( 141 FieldSpec.builder(param.type, param.name, PRIVATE, FINAL).build()); 142 }); 143 factoryBuilder.addMethod(constructor.build()); 144 } 145 constructorParams(ProvisionBinding binding)146 private ImmutableList<ParameterSpec> constructorParams(ProvisionBinding binding) { 147 ImmutableList.Builder<ParameterSpec> params = ImmutableList.builder(); 148 moduleParameter(binding).ifPresent(params::add); 149 frameworkFields(binding).values().forEach(field -> params.add(toParameter(field))); 150 return params.build(); 151 } 152 moduleParameter(ProvisionBinding binding)153 private Optional<ParameterSpec> moduleParameter(ProvisionBinding binding) { 154 if (binding.requiresModuleInstance()) { 155 // TODO(user, dpb): Should this use contributingModule()? 156 TypeName type = TypeName.get(binding.bindingTypeElement().get().asType()); 157 return Optional.of(ParameterSpec.builder(type, "module").build()); 158 } 159 return Optional.empty(); 160 } 161 frameworkFields(ProvisionBinding binding)162 private ImmutableMap<Key, FieldSpec> frameworkFields(ProvisionBinding binding) { 163 UniqueNameSet uniqueFieldNames = new UniqueNameSet(); 164 // TODO(user, dpb): Add a test for the case when a Factory parameter is named "module". 165 if (binding.requiresModuleInstance()) { 166 uniqueFieldNames.claim("module"); 167 } 168 return ImmutableMap.copyOf( 169 transformValues( 170 generateBindingFieldsForDependencies(binding), 171 field -> 172 FieldSpec.builder( 173 field.type(), uniqueFieldNames.getUniqueName(field.name()), PRIVATE, FINAL) 174 .build())); 175 } 176 addCreateMethod(ProvisionBinding binding, TypeSpec.Builder factoryBuilder)177 private void addCreateMethod(ProvisionBinding binding, TypeSpec.Builder factoryBuilder) { 178 // If constructing a factory for @Inject or @Provides bindings, we use a static create method 179 // so that generated components can avoid having to refer to the generic types 180 // of the factory. (Otherwise they may have visibility problems referring to the types.) 181 MethodSpec.Builder createMethodBuilder = 182 methodBuilder("create") 183 .addModifiers(PUBLIC, STATIC) 184 .returns(parameterizedGeneratedTypeNameForBinding(binding)) 185 .addTypeVariables(bindingTypeElementTypeVariableNames(binding)); 186 187 switch (binding.factoryCreationStrategy()) { 188 case SINGLETON_INSTANCE: 189 FieldSpec.Builder instanceFieldBuilder = 190 FieldSpec.builder(nameGeneratedType(binding), "INSTANCE", PRIVATE, STATIC, FINAL) 191 .initializer("new $T()", nameGeneratedType(binding)); 192 193 if (!bindingTypeElementTypeVariableNames(binding).isEmpty()) { 194 // If the factory has type parameters, ignore them in the field declaration & initializer 195 instanceFieldBuilder.addAnnotation(suppressWarnings(RAWTYPES)); 196 197 createMethodBuilder.addAnnotation(suppressWarnings(UNCHECKED)); 198 } 199 createMethodBuilder.addStatement("return INSTANCE"); 200 factoryBuilder.addField(instanceFieldBuilder.build()); 201 break; 202 case CLASS_CONSTRUCTOR: 203 List<ParameterSpec> params = constructorParams(binding); 204 createMethodBuilder.addParameters(params); 205 createMethodBuilder.addStatement( 206 "return new $T($L)", 207 parameterizedGeneratedTypeNameForBinding(binding), 208 makeParametersCodeBlock(Lists.transform(params, input -> CodeBlock.of("$N", input)))); 209 break; 210 default: 211 throw new AssertionError(); 212 } 213 factoryBuilder.addMethod(createMethodBuilder.build()); 214 } 215 getMethod(ProvisionBinding binding)216 private MethodSpec getMethod(ProvisionBinding binding) { 217 TypeName providedTypeName = providedTypeName(binding); 218 MethodSpec.Builder getMethod = 219 methodBuilder("get") 220 .addAnnotation(Override.class) 221 .addModifiers(PUBLIC) 222 .returns(providedTypeName); 223 224 ImmutableMap<Key, FieldSpec> frameworkFields = frameworkFields(binding); 225 CodeBlock parametersCodeBlock = 226 makeParametersCodeBlock( 227 frameworkFieldUsages(binding.provisionDependencies(), frameworkFields).values()); 228 229 if (binding.kind().equals(PROVISION)) { 230 binding 231 .nullableType() 232 .ifPresent(nullableType -> CodeBlocks.addAnnotation(getMethod, nullableType)); 233 getMethod.addStatement( 234 "return $L", 235 ProvisionMethod.invoke( 236 binding, 237 request -> 238 frameworkTypeUsageStatement( 239 CodeBlock.of("$N", frameworkFields.get(request.key())), request.kind()), 240 nameGeneratedType(binding), 241 binding.requiresModuleInstance() 242 ? Optional.of(CodeBlock.of("module")) 243 : Optional.empty(), 244 compilerOptions, 245 elements)); 246 } else if (!binding.injectionSites().isEmpty()) { 247 CodeBlock instance = CodeBlock.of("instance"); 248 getMethod 249 .addStatement("$1T $2L = new $1T($3L)", providedTypeName, instance, parametersCodeBlock) 250 .addCode( 251 InjectionSiteMethod.invokeAll( 252 binding.injectionSites(), 253 nameGeneratedType(binding), 254 instance, 255 binding.key().type(), 256 types, 257 frameworkFieldUsages(binding.dependencies(), frameworkFields)::get, 258 elements)) 259 .addStatement("return $L", instance); 260 } else { 261 getMethod.addStatement( 262 "return new $T($L)", providedTypeName, parametersCodeBlock); 263 } 264 return getMethod.build(); 265 } 266 providedTypeName(ProvisionBinding binding)267 private static TypeName providedTypeName(ProvisionBinding binding) { 268 return TypeName.get(binding.contributedType()); 269 } 270 factoryTypeName(ProvisionBinding binding)271 private static TypeName factoryTypeName(ProvisionBinding binding) { 272 return factoryOf(providedTypeName(binding)); 273 } 274 toParameter(FieldSpec field)275 private static ParameterSpec toParameter(FieldSpec field) { 276 return ParameterSpec.builder(field.type, field.name).build(); 277 } 278 279 /** 280 * Returns {@code Preconditions.checkNotNull(providesMethodInvocation)} with a message suitable 281 * for {@code @Provides} methods. 282 */ checkNotNullProvidesMethod(CodeBlock providesMethodInvocation)283 static CodeBlock checkNotNullProvidesMethod(CodeBlock providesMethodInvocation) { 284 return CodeBlock.of( 285 "$T.checkNotNull($L, $S)", 286 Preconditions.class, 287 providesMethodInvocation, 288 "Cannot return null from a non-@Nullable @Provides method"); 289 } 290 } 291