1 /* 2 * Copyright (C) 2014 Google, Inc. 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 package dagger.internal.codegen; 17 18 import com.google.auto.common.MoreTypes; 19 import com.google.common.base.Joiner; 20 import com.google.common.base.Optional; 21 import com.google.common.collect.ImmutableMap; 22 import com.google.common.collect.ImmutableSet; 23 import com.google.common.collect.Lists; 24 import dagger.MembersInjector; 25 import dagger.Provides.Type; 26 import dagger.internal.Factory; 27 import dagger.internal.codegen.writer.ClassName; 28 import dagger.internal.codegen.writer.ClassWriter; 29 import dagger.internal.codegen.writer.ConstructorWriter; 30 import dagger.internal.codegen.writer.EnumWriter; 31 import dagger.internal.codegen.writer.FieldWriter; 32 import dagger.internal.codegen.writer.JavaWriter; 33 import dagger.internal.codegen.writer.MethodWriter; 34 import dagger.internal.codegen.writer.ParameterizedTypeName; 35 import dagger.internal.codegen.writer.Snippet; 36 import dagger.internal.codegen.writer.StringLiteral; 37 import dagger.internal.codegen.writer.TypeName; 38 import dagger.internal.codegen.writer.TypeNames; 39 import dagger.internal.codegen.writer.TypeVariableName; 40 import dagger.internal.codegen.writer.TypeWriter; 41 import java.util.Collections; 42 import java.util.List; 43 import java.util.Map; 44 import javax.annotation.Generated; 45 import javax.annotation.processing.Filer; 46 import javax.inject.Inject; 47 import javax.lang.model.element.Element; 48 import javax.lang.model.element.Modifier; 49 import javax.lang.model.element.TypeParameterElement; 50 import javax.lang.model.type.TypeMirror; 51 import javax.tools.Diagnostic; 52 53 import static com.google.common.base.Preconditions.checkState; 54 import static dagger.Provides.Type.SET; 55 import static dagger.internal.codegen.ContributionBinding.Kind.PROVISION; 56 import static dagger.internal.codegen.ErrorMessages.CANNOT_RETURN_NULL_FROM_NON_NULLABLE_PROVIDES_METHOD; 57 import static dagger.internal.codegen.SourceFiles.frameworkTypeUsageStatement; 58 import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding; 59 import static dagger.internal.codegen.SourceFiles.parameterizedGeneratedTypeNameForBinding; 60 import static dagger.internal.codegen.writer.Snippet.makeParametersSnippet; 61 import static javax.lang.model.element.Modifier.FINAL; 62 import static javax.lang.model.element.Modifier.PRIVATE; 63 import static javax.lang.model.element.Modifier.PUBLIC; 64 import static javax.lang.model.element.Modifier.STATIC; 65 66 /** 67 * Generates {@link Factory} implementations from {@link ProvisionBinding} instances for 68 * {@link Inject} constructors. 69 * 70 * @author Gregory Kick 71 * @since 2.0 72 */ 73 final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding> { 74 private final DependencyRequestMapper dependencyRequestMapper; 75 private final Diagnostic.Kind nullableValidationType; 76 77 FactoryGenerator(Filer filer, DependencyRequestMapper dependencyRequestMapper, 78 Diagnostic.Kind nullableValidationType) { 79 super(filer); 80 this.dependencyRequestMapper = dependencyRequestMapper; 81 this.nullableValidationType = nullableValidationType; 82 } 83 84 @Override 85 ClassName nameGeneratedType(ProvisionBinding binding) { 86 return generatedClassNameForBinding(binding); 87 } 88 89 @Override 90 Iterable<? extends Element> getOriginatingElements(ProvisionBinding binding) { 91 return ImmutableSet.of(binding.bindingElement()); 92 } 93 94 @Override 95 Optional<? extends Element> getElementForErrorReporting(ProvisionBinding binding) { 96 return Optional.of(binding.bindingElement()); 97 } 98 99 @Override 100 ImmutableSet<JavaWriter> write(ClassName generatedTypeName, ProvisionBinding binding) { 101 // We don't want to write out resolved bindings -- we want to write out the generic version. 102 checkState(!binding.hasNonDefaultTypeParameters()); 103 104 TypeMirror keyType = binding.provisionType().equals(Type.MAP) 105 ? Util.getProvidedValueTypeOfMap(MoreTypes.asDeclared(binding.key().type())) 106 : binding.key().type(); 107 TypeName providedTypeName = TypeNames.forTypeMirror(keyType); 108 JavaWriter writer = JavaWriter.inPackage(generatedTypeName.packageName()); 109 110 final TypeWriter factoryWriter; 111 final Optional<ConstructorWriter> constructorWriter; 112 List<TypeVariableName> typeParameters = Lists.newArrayList(); 113 for (TypeParameterElement typeParameter : binding.bindingTypeElement().getTypeParameters()) { 114 typeParameters.add(TypeVariableName.fromTypeParameterElement(typeParameter)); 115 } 116 switch (binding.factoryCreationStrategy()) { 117 case ENUM_INSTANCE: 118 EnumWriter enumWriter = writer.addEnum(generatedTypeName.simpleName()); 119 enumWriter.addConstant("INSTANCE"); 120 constructorWriter = Optional.absent(); 121 factoryWriter = enumWriter; 122 // If we have type parameters, then remove the parameters from our providedTypeName, 123 // since we'll be implementing an erased version of it. 124 if (!typeParameters.isEmpty()) { 125 factoryWriter.annotate(SuppressWarnings.class).setValue("rawtypes"); 126 providedTypeName = ((ParameterizedTypeName) providedTypeName).type(); 127 } 128 break; 129 case CLASS_CONSTRUCTOR: 130 ClassWriter classWriter = writer.addClass(generatedTypeName.simpleName()); 131 classWriter.addTypeParameters(typeParameters); 132 classWriter.addModifiers(FINAL); 133 constructorWriter = Optional.of(classWriter.addConstructor()); 134 constructorWriter.get().addModifiers(PUBLIC); 135 factoryWriter = classWriter; 136 if (binding.bindingKind().equals(PROVISION) 137 && !binding.bindingElement().getModifiers().contains(STATIC)) { 138 TypeName enclosingType = TypeNames.forTypeMirror(binding.bindingTypeElement().asType()); 139 factoryWriter.addField(enclosingType, "module").addModifiers(PRIVATE, FINAL); 140 constructorWriter.get().addParameter(enclosingType, "module"); 141 constructorWriter.get().body() 142 .addSnippet("assert module != null;") 143 .addSnippet("this.module = module;"); 144 } 145 break; 146 default: 147 throw new AssertionError(); 148 } 149 150 factoryWriter.annotate(Generated.class).setValue(ComponentProcessor.class.getName()); 151 factoryWriter.addModifiers(PUBLIC); 152 factoryWriter.addImplementedType( 153 ParameterizedTypeName.create(ClassName.fromClass(Factory.class), providedTypeName)); 154 155 MethodWriter getMethodWriter = factoryWriter.addMethod(providedTypeName, "get"); 156 getMethodWriter.annotate(Override.class); 157 getMethodWriter.addModifiers(PUBLIC); 158 159 if (binding.membersInjectionRequest().isPresent()) { 160 ParameterizedTypeName membersInjectorType = ParameterizedTypeName.create( 161 MembersInjector.class, providedTypeName); 162 factoryWriter.addField(membersInjectorType, "membersInjector").addModifiers(PRIVATE, FINAL); 163 constructorWriter.get().addParameter(membersInjectorType, "membersInjector"); 164 constructorWriter.get().body() 165 .addSnippet("assert membersInjector != null;") 166 .addSnippet("this.membersInjector = membersInjector;"); 167 } 168 169 ImmutableMap<BindingKey, FrameworkField> fields = 170 SourceFiles.generateBindingFieldsForDependencies( 171 dependencyRequestMapper, binding.dependencies()); 172 173 for (FrameworkField bindingField : fields.values()) { 174 TypeName fieldType = bindingField.frameworkType(); 175 FieldWriter field = factoryWriter.addField(fieldType, bindingField.name()); 176 field.addModifiers(PRIVATE, FINAL); 177 constructorWriter.get().addParameter(field.type(), field.name()); 178 constructorWriter.get().body() 179 .addSnippet("assert %s != null;", field.name()) 180 .addSnippet("this.%1$s = %1$s;", field.name()); 181 } 182 183 // If constructing a factory for @Inject or @Provides bindings, we use a static create method 184 // so that generated components can avoid having to refer to the generic types 185 // of the factory. (Otherwise they may have visibility problems referring to the types.) 186 switch(binding.bindingKind()) { 187 case INJECTION: 188 case PROVISION: 189 // The return type is usually the same as the implementing type, except in the case 190 // of enums with type variables (where we cast). 191 TypeName returnType = ParameterizedTypeName.create(ClassName.fromClass(Factory.class), 192 TypeNames.forTypeMirror(keyType)); 193 MethodWriter createMethodWriter = factoryWriter.addMethod(returnType, "create"); 194 createMethodWriter.addTypeParameters(typeParameters); 195 createMethodWriter.addModifiers(Modifier.PUBLIC, Modifier.STATIC); 196 Map<String, TypeName> params = constructorWriter.isPresent() 197 ? constructorWriter.get().parameters() : ImmutableMap.<String, TypeName>of(); 198 for (Map.Entry<String, TypeName> param : params.entrySet()) { 199 createMethodWriter.addParameter(param.getValue(), param.getKey()); 200 } 201 switch (binding.factoryCreationStrategy()) { 202 case ENUM_INSTANCE: 203 if (typeParameters.isEmpty()) { 204 createMethodWriter.body().addSnippet("return INSTANCE;"); 205 } else { 206 // We use an unsafe cast here because the types are different. 207 // It's safe because the type is never referenced anywhere. 208 createMethodWriter.annotate(SuppressWarnings.class).setValue("unchecked"); 209 createMethodWriter.body().addSnippet("return (Factory) INSTANCE;"); 210 } 211 break; 212 case CLASS_CONSTRUCTOR: 213 createMethodWriter.body().addSnippet("return new %s(%s);", 214 parameterizedGeneratedTypeNameForBinding(binding), 215 Joiner.on(", ").join(params.keySet())); 216 break; 217 default: 218 throw new AssertionError(); 219 } 220 break; 221 default: // do nothing. 222 } 223 224 List<Snippet> parameters = Lists.newArrayList(); 225 for (DependencyRequest dependency : binding.dependencies()) { 226 parameters.add(frameworkTypeUsageStatement( 227 Snippet.format(fields.get(dependency.bindingKey()).name()), dependency.kind())); 228 } 229 Snippet parametersSnippet = makeParametersSnippet(parameters); 230 231 if (binding.bindingKind().equals(PROVISION)) { 232 Snippet providesMethodInvocation = Snippet.format("%s.%s(%s)", 233 binding.bindingElement().getModifiers().contains(STATIC) 234 ? ClassName.fromTypeElement(binding.bindingTypeElement()) 235 : "module", 236 binding.bindingElement().getSimpleName(), 237 parametersSnippet); 238 239 if (binding.provisionType().equals(SET)) { 240 TypeName paramTypeName = TypeNames.forTypeMirror( 241 MoreTypes.asDeclared(keyType).getTypeArguments().get(0)); 242 // TODO(cgruber): only be explicit with the parameter if paramType contains wildcards. 243 getMethodWriter.body().addSnippet("return %s.<%s>singleton(%s);", 244 ClassName.fromClass(Collections.class), paramTypeName, providesMethodInvocation); 245 } else if (binding.nullableType().isPresent() 246 || nullableValidationType.equals(Diagnostic.Kind.WARNING)) { 247 if (binding.nullableType().isPresent()) { 248 getMethodWriter.annotate( 249 (ClassName) TypeNames.forTypeMirror(binding.nullableType().get())); 250 } 251 getMethodWriter.body().addSnippet("return %s;", providesMethodInvocation); 252 } else { 253 StringLiteral failMsg = 254 StringLiteral.forValue(CANNOT_RETURN_NULL_FROM_NON_NULLABLE_PROVIDES_METHOD); 255 getMethodWriter.body().addSnippet(Snippet.format(Joiner.on('\n').join( 256 "%s provided = %s;", 257 "if (provided == null) {", 258 " throw new NullPointerException(%s);", 259 "}", 260 "return provided;"), 261 getMethodWriter.returnType(), 262 providesMethodInvocation, 263 failMsg)); 264 } 265 } else if (binding.membersInjectionRequest().isPresent()) { 266 getMethodWriter.body().addSnippet("%1$s instance = new %1$s(%2$s);", 267 providedTypeName, parametersSnippet); 268 getMethodWriter.body().addSnippet("membersInjector.injectMembers(instance);"); 269 getMethodWriter.body().addSnippet("return instance;"); 270 } else { 271 getMethodWriter.body() 272 .addSnippet("return new %s(%s);", providedTypeName, parametersSnippet); 273 } 274 275 // TODO(gak): write a sensible toString 276 return ImmutableSet.of(writer); 277 } 278 } 279