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.writing; 18 19 import static com.google.common.base.Preconditions.checkState; 20 import static com.squareup.javapoet.MethodSpec.constructorBuilder; 21 import static com.squareup.javapoet.MethodSpec.methodBuilder; 22 import static com.squareup.javapoet.TypeSpec.classBuilder; 23 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors; 24 import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors; 25 import static dagger.internal.codegen.binding.SourceFiles.bindingTypeElementTypeVariableNames; 26 import static dagger.internal.codegen.binding.SourceFiles.frameworkFieldUsages; 27 import static dagger.internal.codegen.binding.SourceFiles.generateBindingFieldsForDependencies; 28 import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType; 29 import static dagger.internal.codegen.binding.SourceFiles.parameterizedGeneratedTypeNameForBinding; 30 import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES; 31 import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; 32 import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings; 33 import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock; 34 import static dagger.internal.codegen.javapoet.TypeNames.membersInjectorOf; 35 import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom; 36 import static dagger.internal.codegen.writing.GwtCompatibility.gwtIncompatibleAnnotation; 37 import static javax.lang.model.element.Modifier.FINAL; 38 import static javax.lang.model.element.Modifier.PRIVATE; 39 import static javax.lang.model.element.Modifier.PUBLIC; 40 import static javax.lang.model.element.Modifier.STATIC; 41 42 import com.google.common.collect.ImmutableList; 43 import com.google.common.collect.ImmutableMap; 44 import com.squareup.javapoet.ClassName; 45 import com.squareup.javapoet.CodeBlock; 46 import com.squareup.javapoet.FieldSpec; 47 import com.squareup.javapoet.MethodSpec; 48 import com.squareup.javapoet.ParameterSpec; 49 import com.squareup.javapoet.TypeName; 50 import com.squareup.javapoet.TypeSpec; 51 import com.squareup.javapoet.TypeVariableName; 52 import dagger.MembersInjector; 53 import dagger.internal.codegen.base.SourceFileGenerator; 54 import dagger.internal.codegen.base.UniqueNameSet; 55 import dagger.internal.codegen.binding.FrameworkField; 56 import dagger.internal.codegen.binding.MembersInjectionBinding; 57 import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite; 58 import dagger.internal.codegen.kotlin.KotlinMetadataUtil; 59 import dagger.internal.codegen.langmodel.DaggerElements; 60 import dagger.internal.codegen.langmodel.DaggerTypes; 61 import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod; 62 import dagger.model.DependencyRequest; 63 import java.util.Map.Entry; 64 import java.util.Optional; 65 import javax.annotation.processing.Filer; 66 import javax.inject.Inject; 67 import javax.lang.model.SourceVersion; 68 import javax.lang.model.element.Element; 69 70 /** 71 * Generates {@link MembersInjector} implementations from {@link MembersInjectionBinding} instances. 72 */ 73 public final class MembersInjectorGenerator extends SourceFileGenerator<MembersInjectionBinding> { 74 private final DaggerTypes types; 75 private final KotlinMetadataUtil metadataUtil; 76 77 @Inject MembersInjectorGenerator( Filer filer, DaggerElements elements, DaggerTypes types, SourceVersion sourceVersion, KotlinMetadataUtil metadataUtil)78 MembersInjectorGenerator( 79 Filer filer, 80 DaggerElements elements, 81 DaggerTypes types, 82 SourceVersion sourceVersion, 83 KotlinMetadataUtil metadataUtil) { 84 super(filer, elements, sourceVersion); 85 this.types = types; 86 this.metadataUtil = metadataUtil; 87 } 88 89 @Override nameGeneratedType(MembersInjectionBinding binding)90 public ClassName nameGeneratedType(MembersInjectionBinding binding) { 91 return membersInjectorNameForType(binding.membersInjectedType()); 92 } 93 94 @Override originatingElement(MembersInjectionBinding binding)95 public Element originatingElement(MembersInjectionBinding binding) { 96 return binding.membersInjectedType(); 97 } 98 99 @Override write(MembersInjectionBinding binding)100 public Optional<TypeSpec.Builder> write(MembersInjectionBinding binding) { 101 // Empty members injection bindings are special and don't need source files. 102 if (binding.injectionSites().isEmpty()) { 103 return Optional.empty(); 104 } 105 106 // Members injectors for classes with no local injection sites and no @Inject 107 // constructor are unused. 108 if (!binding.hasLocalInjectionSites() 109 && injectedConstructors(binding.membersInjectedType()).isEmpty() 110 && assistedInjectedConstructors(binding.membersInjectedType()).isEmpty()) { 111 return Optional.empty(); 112 } 113 114 115 // We don't want to write out resolved bindings -- we want to write out the generic version. 116 checkState( 117 !binding.unresolved().isPresent(), 118 "tried to generate a MembersInjector for a binding of a resolved generic type: %s", 119 binding); 120 121 ClassName generatedTypeName = nameGeneratedType(binding); 122 ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding); 123 TypeSpec.Builder injectorTypeBuilder = 124 classBuilder(generatedTypeName) 125 .addModifiers(PUBLIC, FINAL) 126 .addTypeVariables(typeParameters); 127 128 TypeName injectedTypeName = TypeName.get(binding.key().type()); 129 TypeName implementedType = membersInjectorOf(injectedTypeName); 130 injectorTypeBuilder.addSuperinterface(implementedType); 131 132 MethodSpec.Builder injectMembersBuilder = 133 methodBuilder("injectMembers") 134 .addModifiers(PUBLIC) 135 .addAnnotation(Override.class) 136 .addParameter(injectedTypeName, "instance"); 137 138 ImmutableMap<DependencyRequest, FrameworkField> fields = 139 generateBindingFieldsForDependencies(binding); 140 141 ImmutableMap.Builder<DependencyRequest, FieldSpec> dependencyFieldsBuilder = 142 ImmutableMap.builder(); 143 144 MethodSpec.Builder constructorBuilder = constructorBuilder().addModifiers(PUBLIC); 145 146 // We use a static create method so that generated components can avoid having 147 // to refer to the generic types of the factory. 148 // (Otherwise they may have visibility problems referring to the types.) 149 MethodSpec.Builder createMethodBuilder = 150 methodBuilder("create") 151 .returns(implementedType) 152 .addModifiers(PUBLIC, STATIC) 153 .addTypeVariables(typeParameters); 154 155 createMethodBuilder.addCode( 156 "return new $T(", parameterizedGeneratedTypeNameForBinding(binding)); 157 ImmutableList.Builder<CodeBlock> constructorInvocationParameters = ImmutableList.builder(); 158 159 boolean usesRawFrameworkTypes = false; 160 UniqueNameSet fieldNames = new UniqueNameSet(); 161 for (Entry<DependencyRequest, FrameworkField> fieldEntry : fields.entrySet()) { 162 DependencyRequest dependency = fieldEntry.getKey(); 163 FrameworkField bindingField = fieldEntry.getValue(); 164 165 // If the dependency type is not visible to this members injector, then use the raw framework 166 // type for the field. 167 boolean useRawFrameworkType = 168 !isTypeAccessibleFrom(dependency.key().type(), generatedTypeName.packageName()); 169 170 String fieldName = fieldNames.getUniqueName(bindingField.name()); 171 TypeName fieldType = useRawFrameworkType ? bindingField.type().rawType : bindingField.type(); 172 FieldSpec.Builder fieldBuilder = FieldSpec.builder(fieldType, fieldName, PRIVATE, FINAL); 173 ParameterSpec.Builder parameterBuilder = ParameterSpec.builder(fieldType, fieldName); 174 175 // If we're using the raw type for the field, then suppress the injectMembers method's 176 // unchecked-type warning and the field's and the constructor and create-method's 177 // parameters' raw-type warnings. 178 if (useRawFrameworkType) { 179 usesRawFrameworkTypes = true; 180 fieldBuilder.addAnnotation(suppressWarnings(RAWTYPES)); 181 parameterBuilder.addAnnotation(suppressWarnings(RAWTYPES)); 182 } 183 constructorBuilder.addParameter(parameterBuilder.build()); 184 createMethodBuilder.addParameter(parameterBuilder.build()); 185 186 FieldSpec field = fieldBuilder.build(); 187 injectorTypeBuilder.addField(field); 188 constructorBuilder.addStatement("this.$1N = $1N", field); 189 dependencyFieldsBuilder.put(dependency, field); 190 constructorInvocationParameters.add(CodeBlock.of("$N", field)); 191 } 192 193 createMethodBuilder.addCode( 194 constructorInvocationParameters.build().stream().collect(toParametersCodeBlock())); 195 createMethodBuilder.addCode(");"); 196 197 injectorTypeBuilder.addMethod(constructorBuilder.build()); 198 injectorTypeBuilder.addMethod(createMethodBuilder.build()); 199 200 ImmutableMap<DependencyRequest, FieldSpec> dependencyFields = dependencyFieldsBuilder.build(); 201 202 injectMembersBuilder.addCode( 203 InjectionSiteMethod.invokeAll( 204 binding.injectionSites(), 205 generatedTypeName, 206 CodeBlock.of("instance"), 207 binding.key().type(), 208 frameworkFieldUsages(binding.dependencies(), dependencyFields)::get, 209 types, 210 metadataUtil)); 211 212 if (usesRawFrameworkTypes) { 213 injectMembersBuilder.addAnnotation(suppressWarnings(UNCHECKED)); 214 } 215 injectorTypeBuilder.addMethod(injectMembersBuilder.build()); 216 217 for (InjectionSite injectionSite : binding.injectionSites()) { 218 if (injectionSite.element().getEnclosingElement().equals(binding.membersInjectedType())) { 219 injectorTypeBuilder.addMethod(InjectionSiteMethod.create(injectionSite, metadataUtil)); 220 } 221 } 222 223 gwtIncompatibleAnnotation(binding).ifPresent(injectorTypeBuilder::addAnnotation); 224 225 return Optional.of(injectorTypeBuilder); 226 } 227 } 228