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.MoreTypes;
     19 import com.google.common.base.Joiner;
     20 import com.google.common.base.Optional;
     21 import com.google.common.collect.ImmutableMap;
     22 import com.google.common.collect.ImmutableSet;
     23 import com.google.common.collect.Lists;
     24 import dagger.MembersInjector;
     25 import dagger.Provides.Type;
     26 import dagger.internal.Factory;
     27 import dagger.internal.codegen.writer.ClassName;
     28 import dagger.internal.codegen.writer.ClassWriter;
     29 import dagger.internal.codegen.writer.ConstructorWriter;
     30 import dagger.internal.codegen.writer.EnumWriter;
     31 import dagger.internal.codegen.writer.FieldWriter;
     32 import dagger.internal.codegen.writer.JavaWriter;
     33 import dagger.internal.codegen.writer.MethodWriter;
     34 import dagger.internal.codegen.writer.ParameterizedTypeName;
     35 import dagger.internal.codegen.writer.Snippet;
     36 import dagger.internal.codegen.writer.StringLiteral;
     37 import dagger.internal.codegen.writer.TypeName;
     38 import dagger.internal.codegen.writer.TypeNames;
     39 import dagger.internal.codegen.writer.TypeVariableName;
     40 import dagger.internal.codegen.writer.TypeWriter;
     41 import java.util.Collections;
     42 import java.util.List;
     43 import java.util.Map;
     44 import javax.annotation.Generated;
     45 import javax.annotation.processing.Filer;
     46 import javax.inject.Inject;
     47 import javax.lang.model.element.Element;
     48 import javax.lang.model.element.Modifier;
     49 import javax.lang.model.element.TypeParameterElement;
     50 import javax.lang.model.type.TypeMirror;
     51 import javax.tools.Diagnostic;
     52 
     53 import static com.google.common.base.Preconditions.checkState;
     54 import static dagger.Provides.Type.SET;
     55 import static dagger.internal.codegen.ContributionBinding.Kind.PROVISION;
     56 import static dagger.internal.codegen.ErrorMessages.CANNOT_RETURN_NULL_FROM_NON_NULLABLE_PROVIDES_METHOD;
     57 import static dagger.internal.codegen.SourceFiles.frameworkTypeUsageStatement;
     58 import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding;
     59 import static dagger.internal.codegen.SourceFiles.parameterizedGeneratedTypeNameForBinding;
     60 import static dagger.internal.codegen.writer.Snippet.makeParametersSnippet;
     61 import static javax.lang.model.element.Modifier.FINAL;
     62 import static javax.lang.model.element.Modifier.PRIVATE;
     63 import static javax.lang.model.element.Modifier.PUBLIC;
     64 import static javax.lang.model.element.Modifier.STATIC;
     65 
     66 /**
     67  * Generates {@link Factory} implementations from {@link ProvisionBinding} instances for
     68  * {@link Inject} constructors.
     69  *
     70  * @author Gregory Kick
     71  * @since 2.0
     72  */
     73 final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding> {
     74   private final DependencyRequestMapper dependencyRequestMapper;
     75   private final Diagnostic.Kind nullableValidationType;
     76 
     77   FactoryGenerator(Filer filer, DependencyRequestMapper dependencyRequestMapper,
     78       Diagnostic.Kind nullableValidationType) {
     79     super(filer);
     80     this.dependencyRequestMapper = dependencyRequestMapper;
     81     this.nullableValidationType = nullableValidationType;
     82   }
     83 
     84   @Override
     85   ClassName nameGeneratedType(ProvisionBinding binding) {
     86     return generatedClassNameForBinding(binding);
     87   }
     88 
     89   @Override
     90   Iterable<? extends Element> getOriginatingElements(ProvisionBinding binding) {
     91     return ImmutableSet.of(binding.bindingElement());
     92   }
     93 
     94   @Override
     95   Optional<? extends Element> getElementForErrorReporting(ProvisionBinding binding) {
     96     return Optional.of(binding.bindingElement());
     97   }
     98 
     99   @Override
    100   ImmutableSet<JavaWriter> write(ClassName generatedTypeName, ProvisionBinding binding) {
    101     // We don't want to write out resolved bindings -- we want to write out the generic version.
    102     checkState(!binding.hasNonDefaultTypeParameters());
    103 
    104     TypeMirror keyType = binding.provisionType().equals(Type.MAP)
    105         ? Util.getProvidedValueTypeOfMap(MoreTypes.asDeclared(binding.key().type()))
    106         : binding.key().type();
    107     TypeName providedTypeName = TypeNames.forTypeMirror(keyType);
    108     JavaWriter writer = JavaWriter.inPackage(generatedTypeName.packageName());
    109 
    110     final TypeWriter factoryWriter;
    111     final Optional<ConstructorWriter> constructorWriter;
    112     List<TypeVariableName> typeParameters = Lists.newArrayList();
    113     for (TypeParameterElement typeParameter : binding.bindingTypeElement().getTypeParameters()) {
    114      typeParameters.add(TypeVariableName.fromTypeParameterElement(typeParameter));
    115     }
    116     switch (binding.factoryCreationStrategy()) {
    117       case ENUM_INSTANCE:
    118         EnumWriter enumWriter = writer.addEnum(generatedTypeName.simpleName());
    119         enumWriter.addConstant("INSTANCE");
    120         constructorWriter = Optional.absent();
    121         factoryWriter = enumWriter;
    122         // If we have type parameters, then remove the parameters from our providedTypeName,
    123         // since we'll be implementing an erased version of it.
    124         if (!typeParameters.isEmpty()) {
    125           factoryWriter.annotate(SuppressWarnings.class).setValue("rawtypes");
    126           providedTypeName = ((ParameterizedTypeName) providedTypeName).type();
    127         }
    128         break;
    129       case CLASS_CONSTRUCTOR:
    130         ClassWriter classWriter = writer.addClass(generatedTypeName.simpleName());
    131         classWriter.addTypeParameters(typeParameters);
    132         classWriter.addModifiers(FINAL);
    133         constructorWriter = Optional.of(classWriter.addConstructor());
    134         constructorWriter.get().addModifiers(PUBLIC);
    135         factoryWriter = classWriter;
    136         if (binding.bindingKind().equals(PROVISION)
    137             && !binding.bindingElement().getModifiers().contains(STATIC)) {
    138           TypeName enclosingType = TypeNames.forTypeMirror(binding.bindingTypeElement().asType());
    139           factoryWriter.addField(enclosingType, "module").addModifiers(PRIVATE, FINAL);
    140           constructorWriter.get().addParameter(enclosingType, "module");
    141           constructorWriter.get().body()
    142               .addSnippet("assert module != null;")
    143               .addSnippet("this.module = module;");
    144         }
    145         break;
    146       default:
    147         throw new AssertionError();
    148     }
    149 
    150     factoryWriter.annotate(Generated.class).setValue(ComponentProcessor.class.getName());
    151     factoryWriter.addModifiers(PUBLIC);
    152     factoryWriter.addImplementedType(
    153         ParameterizedTypeName.create(ClassName.fromClass(Factory.class), providedTypeName));
    154 
    155     MethodWriter getMethodWriter = factoryWriter.addMethod(providedTypeName, "get");
    156     getMethodWriter.annotate(Override.class);
    157     getMethodWriter.addModifiers(PUBLIC);
    158 
    159     if (binding.membersInjectionRequest().isPresent()) {
    160       ParameterizedTypeName membersInjectorType = ParameterizedTypeName.create(
    161           MembersInjector.class, providedTypeName);
    162       factoryWriter.addField(membersInjectorType, "membersInjector").addModifiers(PRIVATE, FINAL);
    163       constructorWriter.get().addParameter(membersInjectorType, "membersInjector");
    164       constructorWriter.get().body()
    165           .addSnippet("assert membersInjector != null;")
    166           .addSnippet("this.membersInjector = membersInjector;");
    167     }
    168 
    169     ImmutableMap<BindingKey, FrameworkField> fields =
    170         SourceFiles.generateBindingFieldsForDependencies(
    171             dependencyRequestMapper, binding.dependencies());
    172 
    173     for (FrameworkField bindingField : fields.values()) {
    174       TypeName fieldType = bindingField.frameworkType();
    175       FieldWriter field = factoryWriter.addField(fieldType, bindingField.name());
    176       field.addModifiers(PRIVATE, FINAL);
    177       constructorWriter.get().addParameter(field.type(), field.name());
    178       constructorWriter.get().body()
    179           .addSnippet("assert %s != null;", field.name())
    180           .addSnippet("this.%1$s = %1$s;", field.name());
    181     }
    182 
    183     // If constructing a factory for @Inject or @Provides bindings, we use a static create method
    184     // so that generated components can avoid having to refer to the generic types
    185     // of the factory.  (Otherwise they may have visibility problems referring to the types.)
    186     switch(binding.bindingKind()) {
    187       case INJECTION:
    188       case PROVISION:
    189         // The return type is usually the same as the implementing type, except in the case
    190         // of enums with type variables (where we cast).
    191         TypeName returnType = ParameterizedTypeName.create(ClassName.fromClass(Factory.class),
    192             TypeNames.forTypeMirror(keyType));
    193         MethodWriter createMethodWriter = factoryWriter.addMethod(returnType, "create");
    194         createMethodWriter.addTypeParameters(typeParameters);
    195         createMethodWriter.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
    196         Map<String, TypeName> params = constructorWriter.isPresent()
    197             ? constructorWriter.get().parameters() : ImmutableMap.<String, TypeName>of();
    198         for (Map.Entry<String, TypeName> param : params.entrySet()) {
    199           createMethodWriter.addParameter(param.getValue(), param.getKey());
    200         }
    201         switch (binding.factoryCreationStrategy()) {
    202           case ENUM_INSTANCE:
    203             if (typeParameters.isEmpty()) {
    204               createMethodWriter.body().addSnippet("return INSTANCE;");
    205             } else {
    206               // We use an unsafe cast here because the types are different.
    207               // It's safe because the type is never referenced anywhere.
    208               createMethodWriter.annotate(SuppressWarnings.class).setValue("unchecked");
    209               createMethodWriter.body().addSnippet("return (Factory) INSTANCE;");
    210             }
    211             break;
    212           case CLASS_CONSTRUCTOR:
    213             createMethodWriter.body().addSnippet("return new %s(%s);",
    214                 parameterizedGeneratedTypeNameForBinding(binding),
    215                 Joiner.on(", ").join(params.keySet()));
    216             break;
    217           default:
    218             throw new AssertionError();
    219         }
    220         break;
    221       default: // do nothing.
    222     }
    223 
    224     List<Snippet> parameters = Lists.newArrayList();
    225     for (DependencyRequest dependency : binding.dependencies()) {
    226       parameters.add(frameworkTypeUsageStatement(
    227           Snippet.format(fields.get(dependency.bindingKey()).name()), dependency.kind()));
    228     }
    229     Snippet parametersSnippet = makeParametersSnippet(parameters);
    230 
    231     if (binding.bindingKind().equals(PROVISION)) {
    232       Snippet providesMethodInvocation = Snippet.format("%s.%s(%s)",
    233           binding.bindingElement().getModifiers().contains(STATIC)
    234               ? ClassName.fromTypeElement(binding.bindingTypeElement())
    235               : "module",
    236           binding.bindingElement().getSimpleName(),
    237           parametersSnippet);
    238 
    239       if (binding.provisionType().equals(SET)) {
    240         TypeName paramTypeName = TypeNames.forTypeMirror(
    241             MoreTypes.asDeclared(keyType).getTypeArguments().get(0));
    242         // TODO(cgruber): only be explicit with the parameter if paramType contains wildcards.
    243         getMethodWriter.body().addSnippet("return %s.<%s>singleton(%s);",
    244             ClassName.fromClass(Collections.class), paramTypeName, providesMethodInvocation);
    245       } else if (binding.nullableType().isPresent()
    246           || nullableValidationType.equals(Diagnostic.Kind.WARNING)) {
    247         if (binding.nullableType().isPresent()) {
    248           getMethodWriter.annotate(
    249               (ClassName) TypeNames.forTypeMirror(binding.nullableType().get()));
    250         }
    251         getMethodWriter.body().addSnippet("return %s;", providesMethodInvocation);
    252       } else {
    253         StringLiteral failMsg =
    254             StringLiteral.forValue(CANNOT_RETURN_NULL_FROM_NON_NULLABLE_PROVIDES_METHOD);
    255         getMethodWriter.body().addSnippet(Snippet.format(Joiner.on('\n').join(
    256             "%s provided = %s;",
    257             "if (provided == null) {",
    258             "  throw new NullPointerException(%s);",
    259             "}",
    260             "return provided;"),
    261             getMethodWriter.returnType(),
    262             providesMethodInvocation,
    263             failMsg));
    264       }
    265     } else if (binding.membersInjectionRequest().isPresent()) {
    266       getMethodWriter.body().addSnippet("%1$s instance = new %1$s(%2$s);",
    267           providedTypeName, parametersSnippet);
    268       getMethodWriter.body().addSnippet("membersInjector.injectMembers(instance);");
    269       getMethodWriter.body().addSnippet("return instance;");
    270     } else {
    271       getMethodWriter.body()
    272           .addSnippet("return new %s(%s);", providedTypeName, parametersSnippet);
    273     }
    274 
    275     // TODO(gak): write a sensible toString
    276     return ImmutableSet.of(writer);
    277   }
    278 }
    279