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