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.MoreElements; 19 import com.google.common.base.CaseFormat; 20 import com.google.common.base.Function; 21 import com.google.common.base.Joiner; 22 import com.google.common.base.Optional; 23 import com.google.common.collect.FluentIterable; 24 import com.google.common.collect.ImmutableList; 25 import com.google.common.collect.ImmutableMap; 26 import com.google.common.collect.ImmutableSet; 27 import com.google.common.collect.Lists; 28 import dagger.MembersInjector; 29 import dagger.internal.codegen.MembersInjectionBinding.InjectionSite; 30 import dagger.internal.codegen.writer.ClassName; 31 import dagger.internal.codegen.writer.ClassWriter; 32 import dagger.internal.codegen.writer.ConstructorWriter; 33 import dagger.internal.codegen.writer.FieldWriter; 34 import dagger.internal.codegen.writer.JavaWriter; 35 import dagger.internal.codegen.writer.MethodWriter; 36 import dagger.internal.codegen.writer.Modifiable; 37 import dagger.internal.codegen.writer.ParameterizedTypeName; 38 import dagger.internal.codegen.writer.Snippet; 39 import dagger.internal.codegen.writer.TypeName; 40 import dagger.internal.codegen.writer.TypeNames; 41 import dagger.internal.codegen.writer.TypeVariableName; 42 import dagger.internal.codegen.writer.VariableWriter; 43 import dagger.internal.codegen.writer.VoidName; 44 import dagger.producers.Producer; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.Map.Entry; 48 import java.util.Set; 49 import javax.annotation.Generated; 50 import javax.annotation.processing.Filer; 51 import javax.inject.Provider; 52 import javax.lang.model.element.Element; 53 import javax.lang.model.element.Modifier; 54 import javax.lang.model.element.TypeParameterElement; 55 import javax.lang.model.type.ArrayType; 56 import javax.lang.model.type.DeclaredType; 57 import javax.lang.model.type.TypeVisitor; 58 import javax.lang.model.util.SimpleTypeVisitor7; 59 60 import static com.google.auto.common.MoreElements.getPackage; 61 import static com.google.common.base.Preconditions.checkState; 62 import static com.google.common.collect.Iterables.getOnlyElement; 63 import static dagger.internal.codegen.SourceFiles.frameworkTypeUsageStatement; 64 import static dagger.internal.codegen.SourceFiles.membersInjectorNameForType; 65 import static dagger.internal.codegen.SourceFiles.parameterizedGeneratedTypeNameForBinding; 66 import static dagger.internal.codegen.writer.Snippet.makeParametersSnippet; 67 import static javax.lang.model.element.Modifier.FINAL; 68 import static javax.lang.model.element.Modifier.PRIVATE; 69 import static javax.lang.model.element.Modifier.PUBLIC; 70 import static javax.lang.model.element.Modifier.STATIC; 71 72 /** 73 * Generates {@link MembersInjector} implementations from {@link MembersInjectionBinding} instances. 74 * 75 * @author Gregory Kick 76 * @since 2.0 77 */ 78 final class MembersInjectorGenerator extends SourceFileGenerator<MembersInjectionBinding> { 79 private final DependencyRequestMapper dependencyRequestMapper; 80 81 MembersInjectorGenerator( 82 Filer filer, 83 DependencyRequestMapper dependencyRequestMapper) { 84 super(filer); 85 this.dependencyRequestMapper = dependencyRequestMapper; 86 } 87 88 @Override 89 ClassName nameGeneratedType(MembersInjectionBinding binding) { 90 return membersInjectorNameForType(binding.bindingElement()); 91 } 92 93 @Override 94 Iterable<? extends Element> getOriginatingElements( 95 MembersInjectionBinding binding) { 96 return FluentIterable.from(binding.injectionSites()) 97 .transform(new Function<InjectionSite, Element>() { 98 @Override public Element apply(InjectionSite injectionSite) { 99 return injectionSite.element(); 100 } 101 }) 102 .toSet(); 103 } 104 105 @Override 106 Optional<? extends Element> getElementForErrorReporting(MembersInjectionBinding binding) { 107 return Optional.of(binding.bindingElement()); 108 } 109 110 @Override 111 ImmutableSet<JavaWriter> write(ClassName generatedTypeName, MembersInjectionBinding binding) { 112 // Empty members injection bindings are special and don't need source files. 113 if (binding.injectionSites().isEmpty()) { 114 return ImmutableSet.of(); 115 } 116 Set<String> delegateMethods = new HashSet<>(); 117 118 // We don't want to write out resolved bindings -- we want to write out the generic version. 119 checkState(!binding.hasNonDefaultTypeParameters()); 120 121 TypeName injectedTypeName = TypeNames.forTypeMirror(binding.key().type()); 122 JavaWriter writer = JavaWriter.inPackage(generatedTypeName.packageName()); 123 124 ClassWriter injectorWriter = writer.addClass(generatedTypeName.simpleName()); 125 List<TypeVariableName> typeParameters = Lists.newArrayList(); 126 for (TypeParameterElement typeParameter : binding.bindingTypeElement().getTypeParameters()) { 127 typeParameters.add(TypeVariableName.fromTypeParameterElement(typeParameter)); 128 } 129 injectorWriter.addTypeParameters(typeParameters); 130 injectorWriter.annotate(Generated.class) 131 .setValue(ComponentProcessor.class.getCanonicalName()); 132 injectorWriter.addModifiers(PUBLIC, FINAL); 133 TypeName implementedType = 134 ParameterizedTypeName.create(MembersInjector.class, injectedTypeName); 135 injectorWriter.addImplementedType(implementedType); 136 137 ConstructorWriter constructorWriter = injectorWriter.addConstructor(); 138 constructorWriter.addModifiers(PUBLIC); 139 MethodWriter injectMembersWriter = injectorWriter.addMethod(VoidName.VOID, "injectMembers"); 140 injectMembersWriter.addModifiers(PUBLIC); 141 injectMembersWriter.annotate(Override.class); 142 injectMembersWriter.addParameter(injectedTypeName, "instance"); 143 injectMembersWriter.body().addSnippet(Joiner.on('\n').join( 144 "if (instance == null) {", 145 " throw new NullPointerException(\"Cannot inject members into a null reference\");", 146 "}")); 147 148 ImmutableMap<BindingKey, FrameworkField> fields = 149 SourceFiles.generateBindingFieldsForDependencies( 150 dependencyRequestMapper, ImmutableSet.copyOf(binding.dependencies())); 151 152 ImmutableMap.Builder<BindingKey, FieldWriter> dependencyFieldsBuilder = 153 ImmutableMap.builder(); 154 155 // We use a static create method so that generated components can avoid having 156 // to refer to the generic types of the factory. 157 // (Otherwise they may have visibility problems referring to the types.) 158 MethodWriter createMethodWriter = injectorWriter.addMethod(implementedType, "create"); 159 createMethodWriter.addTypeParameters(typeParameters); 160 createMethodWriter.addModifiers(Modifier.PUBLIC, Modifier.STATIC); 161 162 boolean usesRawFrameworkTypes = false; 163 for (Entry<BindingKey, FrameworkField> fieldEntry : fields.entrySet()) { 164 BindingKey bindingKey = fieldEntry.getKey(); 165 FrameworkField bindingField = fieldEntry.getValue(); 166 167 // If the dependency type is not visible to this members injector, then use the raw framework 168 // type for the field. 169 boolean useRawFrameworkType = 170 !VISIBLE_TO_MEMBERS_INJECTOR.visit(bindingKey.key().type(), binding); 171 172 FieldWriter field = 173 injectorWriter.addField( 174 useRawFrameworkType 175 ? bindingField.frameworkType().type() 176 : bindingField.frameworkType(), 177 bindingField.name()); 178 179 field.addModifiers(PRIVATE, FINAL); 180 VariableWriter constructorParameter = 181 constructorWriter.addParameter(field.type(), field.name()); 182 VariableWriter createMethodParameter = 183 createMethodWriter.addParameter(constructorParameter.type(), constructorParameter.name()); 184 185 // If we're using the raw type for the field, then suppress the injectMembers method's 186 // unchecked-type warning and the field's and the constructor and create-method's 187 // parameters' raw-type warnings. 188 if (useRawFrameworkType) { 189 usesRawFrameworkTypes = true; 190 suppressRawTypesWarning(field); 191 suppressRawTypesWarning(constructorParameter); 192 suppressRawTypesWarning(createMethodParameter); 193 } 194 195 constructorWriter.body().addSnippet("assert %s != null;", field.name()); 196 constructorWriter.body().addSnippet("this.%1$s = %1$s;", field.name()); 197 dependencyFieldsBuilder.put(bindingKey, field); 198 } 199 200 createMethodWriter 201 .body() 202 .addSnippet( 203 " return new %s(%s);", 204 parameterizedGeneratedTypeNameForBinding(binding), 205 Joiner.on(", ").join(constructorWriter.parameters().keySet())); 206 207 ImmutableMap<BindingKey, FieldWriter> dependencyFields = dependencyFieldsBuilder.build(); 208 for (InjectionSite injectionSite : binding.injectionSites()) { 209 injectMembersWriter 210 .body() 211 .addSnippet( 212 visibleToMembersInjector(binding, injectionSite.element()) 213 ? directInjectMemberSnippet(binding, dependencyFields, injectionSite) 214 : delegateInjectMemberSnippet(dependencyFields, injectionSite)); 215 if (!injectionSite.element().getModifiers().contains(PUBLIC) 216 && injectionSite.element().getEnclosingElement().equals(binding.bindingElement()) 217 && delegateMethods.add(injectionSiteDelegateMethodName(injectionSite.element()))) { 218 writeInjectorMethodForSubclasses( 219 injectorWriter, 220 dependencyFields, 221 typeParameters, 222 injectedTypeName, 223 injectionSite.element(), 224 injectionSite.dependencies()); 225 } 226 } 227 228 if (usesRawFrameworkTypes) { 229 injectMembersWriter.annotate(SuppressWarnings.class).setValue("unchecked"); 230 } 231 232 return ImmutableSet.of(writer); 233 } 234 235 /** 236 * Returns {@code true} if {@code element} is visible to the members injector for {@code binding}. 237 */ 238 // TODO(dpb,gak): Make sure that all cases are covered here. E.g., what if element is public but 239 // enclosed in a package-private element? 240 private static boolean visibleToMembersInjector( 241 MembersInjectionBinding binding, Element element) { 242 return getPackage(element).equals(getPackage(binding.bindingElement())) 243 || element.getModifiers().contains(PUBLIC); 244 } 245 246 /** 247 * Returns a snippet that directly injects the instance's field or method. 248 */ 249 private Snippet directInjectMemberSnippet( 250 MembersInjectionBinding binding, 251 ImmutableMap<BindingKey, FieldWriter> dependencyFields, 252 InjectionSite injectionSite) { 253 return Snippet.format( 254 injectionSite.element().getKind().isField() ? "%s.%s = %s;" : "%s.%s(%s);", 255 getInstanceSnippetWithPotentialCast( 256 injectionSite.element().getEnclosingElement(), binding.bindingElement()), 257 injectionSite.element().getSimpleName(), 258 makeParametersSnippet( 259 parameterSnippets(dependencyFields, injectionSite.dependencies(), true))); 260 } 261 262 /** 263 * Returns a snippet that injects the instance's field or method by calling a static method on the 264 * parent members injector class. 265 */ 266 private Snippet delegateInjectMemberSnippet( 267 ImmutableMap<BindingKey, FieldWriter> dependencyFields, InjectionSite injectionSite) { 268 return Snippet.format( 269 "%s.%s(%s);", 270 membersInjectorNameForType( 271 MoreElements.asType(injectionSite.element().getEnclosingElement())), 272 injectionSiteDelegateMethodName(injectionSite.element()), 273 makeParametersSnippet( 274 new ImmutableList.Builder<Snippet>() 275 .add(Snippet.format("instance")) 276 .addAll(parameterSnippets(dependencyFields, injectionSite.dependencies(), false)) 277 .build())); 278 } 279 280 /** 281 * Returns the parameters for injecting a member. 282 * 283 * @param passValue if {@code true}, each parameter snippet will be the result of converting the 284 * field from the framework type ({@link Provider}, {@link Producer}, etc.) to the real value; 285 * if {@code false}, each parameter snippet will be just the field 286 */ 287 private ImmutableList<Snippet> parameterSnippets( 288 ImmutableMap<BindingKey, FieldWriter> dependencyFields, 289 ImmutableSet<DependencyRequest> dependencies, 290 boolean passValue) { 291 ImmutableList.Builder<Snippet> parameters = ImmutableList.builder(); 292 for (DependencyRequest dependency : dependencies) { 293 Snippet fieldSnippet = 294 Snippet.format("%s", dependencyFields.get(dependency.bindingKey()).name()); 295 parameters.add( 296 passValue ? frameworkTypeUsageStatement(fieldSnippet, dependency.kind()) : fieldSnippet); 297 } 298 return parameters.build(); 299 } 300 301 private Snippet getInstanceSnippetWithPotentialCast( 302 Element injectionSiteElement, Element bindingElement) { 303 return (injectionSiteElement.equals(bindingElement)) 304 ? Snippet.format("instance") 305 : Snippet.format("((%s)instance)", injectionSiteElement); 306 } 307 308 private String injectionSiteDelegateMethodName(Element injectionSiteElement) { 309 return "inject" 310 + CaseFormat.LOWER_CAMEL.to( 311 CaseFormat.UPPER_CAMEL, injectionSiteElement.getSimpleName().toString()); 312 } 313 314 private void writeInjectorMethodForSubclasses( 315 ClassWriter injectorWriter, 316 ImmutableMap<BindingKey, FieldWriter> dependencyFields, 317 List<TypeVariableName> typeParameters, 318 TypeName injectedTypeName, 319 Element injectionElement, 320 ImmutableSet<DependencyRequest> dependencies) { 321 MethodWriter methodWriter = 322 injectorWriter.addMethod(VoidName.VOID, injectionSiteDelegateMethodName(injectionElement)); 323 methodWriter.addModifiers(PUBLIC, STATIC); 324 methodWriter.addParameter(injectedTypeName, "instance"); 325 methodWriter.addTypeParameters(typeParameters); 326 ImmutableList.Builder<Snippet> providedParameters = ImmutableList.builder(); 327 Set<String> parameterNames = new HashSet<>(); 328 for (DependencyRequest dependency : dependencies) { 329 FieldWriter field = dependencyFields.get(dependency.bindingKey()); 330 VariableWriter parameter = 331 methodWriter.addParameter( 332 field.type(), 333 staticInjectMethodDependencyParameterName(parameterNames, dependency, field)); 334 providedParameters.add( 335 frameworkTypeUsageStatement(Snippet.format("%s", parameter.name()), dependency.kind())); 336 } 337 if (injectionElement.getKind().isField()) { 338 methodWriter 339 .body() 340 .addSnippet( 341 "instance.%s = %s;", 342 injectionElement.getSimpleName(), 343 getOnlyElement(providedParameters.build())); 344 } else { 345 methodWriter 346 .body() 347 .addSnippet( 348 "instance.%s(%s);", 349 injectionElement.getSimpleName(), 350 makeParametersSnippet(providedParameters.build())); 351 } 352 } 353 354 /** 355 * Returns the static inject method parameter name for a dependency. 356 * 357 * @param parameterNames the parameter names used so far 358 * @param dependency the dependency 359 * @param field the field used to hold the framework type for the dependency 360 */ 361 private String staticInjectMethodDependencyParameterName( 362 Set<String> parameterNames, DependencyRequest dependency, FieldWriter field) { 363 StringBuilder parameterName = 364 new StringBuilder(dependency.requestElement().getSimpleName().toString()); 365 switch (dependency.kind()) { 366 case LAZY: 367 case INSTANCE: 368 case FUTURE: 369 String suffix = ((ParameterizedTypeName) field.type()).type().simpleName(); 370 if (parameterName.length() <= suffix.length() 371 || !parameterName.substring(parameterName.length() - suffix.length()).equals(suffix)) { 372 parameterName.append(suffix); 373 } 374 break; 375 376 default: 377 break; 378 } 379 int baseLength = parameterName.length(); 380 for (int i = 2; !parameterNames.add(parameterName.toString()); i++) { 381 parameterName.replace(baseLength, parameterName.length(), String.valueOf(i)); 382 } 383 return parameterName.toString(); 384 } 385 386 private void suppressRawTypesWarning(Modifiable modifiable) { 387 modifiable.annotate(SuppressWarnings.class).setValue("rawtypes"); 388 } 389 390 private static final TypeVisitor<Boolean, MembersInjectionBinding> VISIBLE_TO_MEMBERS_INJECTOR = 391 new SimpleTypeVisitor7<Boolean, MembersInjectionBinding>(true) { 392 @Override 393 public Boolean visitArray(ArrayType t, MembersInjectionBinding p) { 394 return visit(t.getComponentType(), p); 395 } 396 397 @Override 398 public Boolean visitDeclared(DeclaredType t, MembersInjectionBinding p) { 399 return visibleToMembersInjector(p, t.asElement()); 400 } 401 }; 402 } 403