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