blob: 8630ce6caa2c877fa51a2e75abc6c46c194d8cee [file] [log] [blame]
/*
* Copyright (C) 2014 The Dagger Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dagger.internal.codegen.writing;
import static com.google.common.base.Preconditions.checkState;
import static com.squareup.javapoet.MethodSpec.constructorBuilder;
import static com.squareup.javapoet.MethodSpec.methodBuilder;
import static com.squareup.javapoet.TypeSpec.classBuilder;
import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors;
import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors;
import static dagger.internal.codegen.binding.SourceFiles.bindingTypeElementTypeVariableNames;
import static dagger.internal.codegen.binding.SourceFiles.frameworkFieldUsages;
import static dagger.internal.codegen.binding.SourceFiles.generateBindingFieldsForDependencies;
import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType;
import static dagger.internal.codegen.binding.SourceFiles.parameterizedGeneratedTypeNameForBinding;
import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES;
import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED;
import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings;
import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock;
import static dagger.internal.codegen.javapoet.TypeNames.membersInjectorOf;
import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom;
import static dagger.internal.codegen.writing.GwtCompatibility.gwtIncompatibleAnnotation;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import dagger.MembersInjector;
import dagger.internal.codegen.base.SourceFileGenerator;
import dagger.internal.codegen.base.UniqueNameSet;
import dagger.internal.codegen.binding.FrameworkField;
import dagger.internal.codegen.binding.MembersInjectionBinding;
import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite;
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
import dagger.internal.codegen.langmodel.DaggerElements;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod;
import dagger.model.DependencyRequest;
import java.util.Map.Entry;
import javax.annotation.processing.Filer;
import javax.inject.Inject;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
/**
* Generates {@link MembersInjector} implementations from {@link MembersInjectionBinding} instances.
*/
public final class MembersInjectorGenerator extends SourceFileGenerator<MembersInjectionBinding> {
private final DaggerTypes types;
private final KotlinMetadataUtil metadataUtil;
@Inject
MembersInjectorGenerator(
Filer filer,
DaggerElements elements,
DaggerTypes types,
SourceVersion sourceVersion,
KotlinMetadataUtil metadataUtil) {
super(filer, elements, sourceVersion);
this.types = types;
this.metadataUtil = metadataUtil;
}
@Override
public Element originatingElement(MembersInjectionBinding binding) {
return binding.membersInjectedType();
}
@Override
public ImmutableList<TypeSpec.Builder> topLevelTypes(MembersInjectionBinding binding) {
// Empty members injection bindings are special and don't need source files.
if (binding.injectionSites().isEmpty()) {
return ImmutableList.of();
}
// Members injectors for classes with no local injection sites and no @Inject
// constructor are unused.
if (!binding.hasLocalInjectionSites()
&& injectedConstructors(binding.membersInjectedType()).isEmpty()
&& assistedInjectedConstructors(binding.membersInjectedType()).isEmpty()) {
return ImmutableList.of();
}
// We don't want to write out resolved bindings -- we want to write out the generic version.
checkState(
!binding.unresolved().isPresent(),
"tried to generate a MembersInjector for a binding of a resolved generic type: %s",
binding);
ClassName generatedTypeName = membersInjectorNameForType(binding.membersInjectedType());
ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding);
TypeSpec.Builder injectorTypeBuilder =
classBuilder(generatedTypeName)
.addModifiers(PUBLIC, FINAL)
.addTypeVariables(typeParameters);
TypeName injectedTypeName = TypeName.get(binding.key().type());
TypeName implementedType = membersInjectorOf(injectedTypeName);
injectorTypeBuilder.addSuperinterface(implementedType);
MethodSpec.Builder injectMembersBuilder =
methodBuilder("injectMembers")
.addModifiers(PUBLIC)
.addAnnotation(Override.class)
.addParameter(injectedTypeName, "instance");
ImmutableMap<DependencyRequest, FrameworkField> fields =
generateBindingFieldsForDependencies(binding);
ImmutableMap.Builder<DependencyRequest, FieldSpec> dependencyFieldsBuilder =
ImmutableMap.builder();
MethodSpec.Builder constructorBuilder = constructorBuilder().addModifiers(PUBLIC);
// We use a static create method so that generated components can avoid having
// to refer to the generic types of the factory.
// (Otherwise they may have visibility problems referring to the types.)
MethodSpec.Builder createMethodBuilder =
methodBuilder("create")
.returns(implementedType)
.addModifiers(PUBLIC, STATIC)
.addTypeVariables(typeParameters);
createMethodBuilder.addCode(
"return new $T(", parameterizedGeneratedTypeNameForBinding(binding));
ImmutableList.Builder<CodeBlock> constructorInvocationParameters = ImmutableList.builder();
boolean usesRawFrameworkTypes = false;
UniqueNameSet fieldNames = new UniqueNameSet();
for (Entry<DependencyRequest, FrameworkField> fieldEntry : fields.entrySet()) {
DependencyRequest dependency = fieldEntry.getKey();
FrameworkField bindingField = fieldEntry.getValue();
// If the dependency type is not visible to this members injector, then use the raw framework
// type for the field.
boolean useRawFrameworkType =
!isTypeAccessibleFrom(dependency.key().type(), generatedTypeName.packageName());
String fieldName = fieldNames.getUniqueName(bindingField.name());
TypeName fieldType = useRawFrameworkType ? bindingField.type().rawType : bindingField.type();
FieldSpec.Builder fieldBuilder = FieldSpec.builder(fieldType, fieldName, PRIVATE, FINAL);
ParameterSpec.Builder parameterBuilder = ParameterSpec.builder(fieldType, fieldName);
// If we're using the raw type for the field, then suppress the injectMembers method's
// unchecked-type warning and the field's and the constructor and create-method's
// parameters' raw-type warnings.
if (useRawFrameworkType) {
usesRawFrameworkTypes = true;
fieldBuilder.addAnnotation(suppressWarnings(RAWTYPES));
parameterBuilder.addAnnotation(suppressWarnings(RAWTYPES));
}
constructorBuilder.addParameter(parameterBuilder.build());
createMethodBuilder.addParameter(parameterBuilder.build());
FieldSpec field = fieldBuilder.build();
injectorTypeBuilder.addField(field);
constructorBuilder.addStatement("this.$1N = $1N", field);
dependencyFieldsBuilder.put(dependency, field);
constructorInvocationParameters.add(CodeBlock.of("$N", field));
}
createMethodBuilder.addCode(
constructorInvocationParameters.build().stream().collect(toParametersCodeBlock()));
createMethodBuilder.addCode(");");
injectorTypeBuilder.addMethod(constructorBuilder.build());
injectorTypeBuilder.addMethod(createMethodBuilder.build());
ImmutableMap<DependencyRequest, FieldSpec> dependencyFields = dependencyFieldsBuilder.build();
injectMembersBuilder.addCode(
InjectionSiteMethod.invokeAll(
binding.injectionSites(),
generatedTypeName,
CodeBlock.of("instance"),
binding.key().type(),
frameworkFieldUsages(binding.dependencies(), dependencyFields)::get,
types,
metadataUtil));
if (usesRawFrameworkTypes) {
injectMembersBuilder.addAnnotation(suppressWarnings(UNCHECKED));
}
injectorTypeBuilder.addMethod(injectMembersBuilder.build());
for (InjectionSite injectionSite : binding.injectionSites()) {
if (injectionSite.element().getEnclosingElement().equals(binding.membersInjectedType())) {
injectorTypeBuilder.addMethod(InjectionSiteMethod.create(injectionSite, metadataUtil));
}
}
gwtIncompatibleAnnotation(binding).ifPresent(injectorTypeBuilder::addAnnotation);
return ImmutableList.of(injectorTypeBuilder);
}
}