blob: b3a7893708ac2ca9d3493592bbd3479f3283cd6e [file] [log] [blame]
/*
* Copyright (C) 2015 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;
import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
import static com.google.common.base.Preconditions.checkState;
import static com.squareup.javapoet.MethodSpec.constructorBuilder;
import static com.squareup.javapoet.MethodSpec.methodBuilder;
import static dagger.internal.codegen.AnnotationSpecs.Suppression.UNCHECKED;
import static dagger.internal.codegen.TypeSpecs.addSupertype;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import com.google.auto.common.MoreTypes;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.util.Elements;
/** Creates the implementation class for a component or subcomponent. */
abstract class AbstractComponentWriter {
// TODO(dpb): Make all these fields private after refactoring is complete.
protected final Elements elements;
protected final DaggerTypes types;
private final CompilerOptions compilerOptions;
protected final BindingGraph graph;
protected final SubcomponentNames subcomponentNames;
private final ComponentBindingExpressions bindingExpressions;
protected final ComponentRequirementFields componentRequirementFields;
protected final GeneratedComponentModel generatedComponentModel;
private final MembersInjectionMethods membersInjectionMethods;
protected final List<MethodSpec> interfaceMethods = new ArrayList<>();
private final ComponentRequirementField.Factory componentRequirementFieldFactory;
protected final MethodSpec.Builder constructor = constructorBuilder().addModifiers(PRIVATE);
private final OptionalFactories optionalFactories;
private ComponentBuilder builder;
private boolean done;
/**
* For each component requirement, the builder field. This map is empty for subcomponents that do
* not use a builder.
*/
private final ImmutableMap<ComponentRequirement, FieldSpec> builderFields;
AbstractComponentWriter(
DaggerTypes types,
Elements elements,
CompilerOptions compilerOptions,
BindingGraph graph,
GeneratedComponentModel generatedComponentModel,
SubcomponentNames subcomponentNames,
OptionalFactories optionalFactories,
ComponentBindingExpressions bindingExpressions,
ComponentRequirementFields componentRequirementFields) {
this.types = types;
this.elements = elements;
this.compilerOptions = compilerOptions;
this.graph = graph;
this.subcomponentNames = subcomponentNames;
this.generatedComponentModel = generatedComponentModel;
this.optionalFactories = optionalFactories;
this.bindingExpressions = bindingExpressions;
// TODO(dpb): Allow ComponentBuilder.create to return a no-op object
if (hasBuilder(graph)) {
builder =
ComponentBuilder.create(
generatedComponentModel.name(), graph, subcomponentNames, elements, types);
builderFields = builder.builderFields();
} else {
builderFields = ImmutableMap.of();
}
this.componentRequirementFields = componentRequirementFields;
// TODO(user): Remove membersInjectionMethods field once we have another way to order methods.
this.membersInjectionMethods = bindingExpressions.membersInjectionMethods();
this.componentRequirementFieldFactory =
new ComponentRequirementField.Factory(generatedComponentModel, builderFields);
}
protected AbstractComponentWriter(
AbstractComponentWriter parent,
GeneratedComponentModel generatedComponentModel,
BindingGraph graph,
ComponentRequirementFields componentRequirementFields) {
this(
parent.types,
parent.elements,
parent.compilerOptions,
graph,
generatedComponentModel,
parent.subcomponentNames,
parent.optionalFactories,
parent.bindingExpressions.forChildComponent(
graph, generatedComponentModel, componentRequirementFields),
componentRequirementFields);
}
/**
* Constructs a {@link TypeSpec.Builder} that models the {@link BindingGraph} for this component.
* This is only intended to be called once (and will throw on successive invocations). If the
* component must be regenerated, use a new instance.
*/
final TypeSpec.Builder write() {
checkState(!done, "ComponentWriter has already been generated.");
addSupertype(generatedComponentModel.component, graph.componentType());
if (hasBuilder(graph)) {
addBuilder();
}
getLocalAndInheritedMethods(
graph.componentDescriptor().componentDefinitionType(), types, elements)
.forEach(method -> generatedComponentModel.claimMethodName(method.getSimpleName()));
addFactoryMethods();
createComponentRequirementFields();
implementInterfaceMethods();
addSubcomponents();
writeInitializeAndInterfaceMethods();
generatedComponentModel.addMethods(membersInjectionMethods.getAllMethods());
generatedComponentModel.addMethod(constructor.build());
if (graph.componentDescriptor().kind().isTopLevel()) {
// TODO(user): pass in generatedComponentModel instead of the component.
optionalFactories.addMembers(generatedComponentModel.component);
}
done = true;
return generatedComponentModel.component;
}
private static boolean hasBuilder(BindingGraph graph) {
ComponentDescriptor component = graph.componentDescriptor();
return component.kind().isTopLevel() || component.builderSpec().isPresent();
}
/**
* Adds a builder type.
*/
private void addBuilder() {
addBuilderClass(builder.typeSpec());
constructor.addParameter(builderName(), "builder");
}
/**
* Adds {@code builder} as a nested builder class. Root components and subcomponents will nest
* this in different classes.
*/
protected abstract void addBuilderClass(TypeSpec builder);
protected final ClassName builderName() {
return builder.name();
}
/** Adds component factory methods. */
protected abstract void addFactoryMethods();
private void createComponentRequirementFields() {
builderFields
.keySet()
.stream()
.map(componentRequirementFieldFactory::forBuilderField)
.forEach(componentRequirementFields::add);
}
private void implementInterfaceMethods() {
Set<MethodSignature> interfaceMethodSignatures = Sets.newHashSet();
DeclaredType componentType = MoreTypes.asDeclared(graph.componentType().asType());
for (ComponentMethodDescriptor componentMethod :
graph.componentDescriptor().componentMethods()) {
if (componentMethod.dependencyRequest().isPresent()) {
ExecutableElement methodElement = componentMethod.methodElement();
ExecutableType requestType =
MoreTypes.asExecutable(types.asMemberOf(componentType, methodElement));
MethodSignature signature =
MethodSignature.fromExecutableType(
methodElement.getSimpleName().toString(), requestType);
if (interfaceMethodSignatures.add(signature)) {
MethodSpec.Builder interfaceMethod =
MethodSpec.overriding(methodElement, componentType, types);
interfaceMethod.addCode(
bindingExpressions.getComponentMethodImplementation(
componentMethod, generatedComponentModel.name()));
interfaceMethods.add(interfaceMethod.build());
}
}
}
}
private void addSubcomponents() {
for (BindingGraph subgraph : graph.subgraphs()) {
generatedComponentModel.addType(new SubcomponentWriter(this, subgraph).write().build());
}
}
private static final int INITIALIZATIONS_PER_INITIALIZE_METHOD = 100;
private void writeInitializeAndInterfaceMethods() {
List<List<CodeBlock>> partitions =
Lists.partition(
generatedComponentModel.getInitializations(), INITIALIZATIONS_PER_INITIALIZE_METHOD);
UniqueNameSet methodNames = new UniqueNameSet();
for (List<CodeBlock> partition : partitions) {
String methodName = methodNames.getUniqueName("initialize");
MethodSpec.Builder initializeMethod =
methodBuilder(methodName)
.addModifiers(PRIVATE)
/* TODO(gak): Strictly speaking, we only need the suppression here if we are also
* initializing a raw field in this method, but the structure of this code makes it
* awkward to pass that bit through. This will be cleaned up when we no longer
* separate fields and initilization as we do now. */
.addAnnotation(AnnotationSpecs.suppressWarnings(UNCHECKED))
.addCode(CodeBlocks.concat(partition));
if (hasBuilder(graph)) {
initializeMethod.addParameter(builderName(), "builder", FINAL);
constructor.addStatement("$L(builder)", methodName);
} else {
constructor.addStatement("$L()", methodName);
}
generatedComponentModel.addMethod(initializeMethod.build());
}
generatedComponentModel.addMethods(interfaceMethods);
}
}