Home | History | Annotate | Download | only in codegen
      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