blob: 7f8f317576a71946657117039a5588297a6a142d [file] [log] [blame]
/*
* Copyright (C) 2017 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.isAnnotationPresent;
import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.CaseFormat.UPPER_CAMEL;
import static com.google.common.base.Preconditions.checkArgument;
import static com.squareup.javapoet.MethodSpec.constructorBuilder;
import static dagger.internal.codegen.ComponentGenerator.componentName;
import static dagger.internal.codegen.ComponentKind.annotationsFor;
import static dagger.internal.codegen.ComponentKind.rootComponentKinds;
import static dagger.internal.codegen.TypeSpecs.addSupertype;
import static javax.lang.model.element.Modifier.ABSTRACT;
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 static javax.lang.model.util.ElementFilter.methodsIn;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import dagger.BindsInstance;
import dagger.internal.codegen.ComponentDescriptor.Factory;
import dagger.internal.codegen.ComponentValidator.ComponentValidationReport;
import dagger.producers.internal.CancellationListener;
import java.lang.annotation.Annotation;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.inject.Inject;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
/**
* A processing step that emits the API of a generated component, without any actual implementation.
*
* <p>When compiling a header jar (hjar), Bazel needs to run annotation processors that generate
* API, like Dagger, to see what code they might output. Full {@link BindingGraph} analysis is
* costly and unnecessary from the perspective of the header compiler; it's sole goal is to pass
* along a slimmed down version of what will be the jar for a particular compilation, whether or not
* that compilation succeeds. If it does not, the compilation pipeline will fail, even if header
* compilation succeeded.
*
* <p>The components emitted by this processing step include all of the API elements exposed by the
* normal step. Method bodies are omitted as Turbine ignores them entirely.
*/
final class ComponentHjarProcessingStep extends TypeCheckingProcessingStep<TypeElement> {
private final SourceVersion sourceVersion;
private final DaggerElements elements;
private final DaggerTypes types;
private final Filer filer;
private final Messager messager;
private final ComponentValidator componentValidator;
private final ComponentDescriptor.Factory componentDescriptorFactory;
@Inject
ComponentHjarProcessingStep(
SourceVersion sourceVersion,
DaggerElements elements,
DaggerTypes types,
Filer filer,
Messager messager,
ComponentValidator componentValidator,
Factory componentDescriptorFactory) {
super(MoreElements::asType);
this.sourceVersion = sourceVersion;
this.elements = elements;
this.types = types;
this.filer = filer;
this.messager = messager;
this.componentValidator = componentValidator;
this.componentDescriptorFactory = componentDescriptorFactory;
}
@Override
public Set<Class<? extends Annotation>> annotations() {
return annotationsFor(rootComponentKinds());
}
@Override
protected void process(
TypeElement componentTypeElement, ImmutableSet<Class<? extends Annotation>> annotations) {
// TODO(ronshapiro): component validation might not be necessary. We should measure it and
// figure out if it's worth seeing if removing it will still work. We could potentially add a
// new catch clause for any exception that's not TypeNotPresentException and ignore the
// component entirely in that case.
ComponentValidationReport validationReport =
componentValidator.validate(componentTypeElement, ImmutableSet.of(), ImmutableSet.of());
validationReport.report().printMessagesTo(messager);
if (validationReport.report().isClean()) {
new EmptyComponentGenerator(filer, elements, sourceVersion)
.generate(componentDescriptorFactory.forTypeElement(componentTypeElement), messager);
}
}
private final class EmptyComponentGenerator extends SourceFileGenerator<ComponentDescriptor> {
EmptyComponentGenerator(Filer filer, DaggerElements elements, SourceVersion sourceVersion) {
super(filer, elements, sourceVersion);
}
@Override
ClassName nameGeneratedType(ComponentDescriptor input) {
return componentName(input.typeElement());
}
@Override
Element originatingElement(ComponentDescriptor input) {
return input.typeElement();
}
@Override
Optional<TypeSpec.Builder> write(
ClassName generatedTypeName, ComponentDescriptor componentDescriptor) {
TypeSpec.Builder generatedComponent =
TypeSpec.classBuilder(generatedTypeName)
.addModifiers(PUBLIC, FINAL)
.addMethod(privateConstructor());
TypeElement componentElement = componentDescriptor.typeElement();
addSupertype(generatedComponent, componentElement);
TypeName builderMethodReturnType;
if (componentDescriptor.creatorDescriptor().isPresent()) {
builderMethodReturnType =
ClassName.get(componentDescriptor.creatorDescriptor().get().typeElement());
} else {
TypeSpec.Builder builder =
TypeSpec.classBuilder("Builder")
.addModifiers(PUBLIC, STATIC, FINAL)
.addMethod(privateConstructor());
ClassName builderClassName = generatedTypeName.nestedClass("Builder");
builderMethodReturnType = builderClassName;
componentRequirements(componentDescriptor)
.map(requirement -> builderInstanceMethod(requirement.typeElement(), builderClassName))
.forEach(builder::addMethod);
builder.addMethod(builderBuildMethod(componentDescriptor));
generatedComponent.addType(builder.build());
}
generatedComponent.addMethod(staticBuilderMethod(builderMethodReturnType));
if (componentRequirements(componentDescriptor)
.noneMatch(requirement -> requirement.requiresAPassedInstance(elements, types))
&& !hasBindsInstanceMethods(componentDescriptor)) {
generatedComponent.addMethod(createMethod(componentDescriptor));
}
DeclaredType componentType = MoreTypes.asDeclared(componentElement.asType());
// TODO(ronshapiro): unify with ComponentImplementationBuilder
Set<MethodSignature> methodSignatures =
Sets.newHashSetWithExpectedSize(componentDescriptor.componentMethods().size());
componentDescriptor
.componentMethods()
.stream()
.filter(
method -> {
return methodSignatures.add(
MethodSignature.forComponentMethod(method, componentType, types));
})
.forEach(
method ->
generatedComponent.addMethod(
emptyComponentMethod(componentElement, method.methodElement())));
if (componentDescriptor.kind().isProducer()) {
generatedComponent
.addSuperinterface(ClassName.get(CancellationListener.class))
.addMethod(onProducerFutureCancelledMethod());
}
return Optional.of(generatedComponent);
}
}
private MethodSpec emptyComponentMethod(TypeElement typeElement, ExecutableElement baseMethod) {
return MethodSpec.overriding(baseMethod, MoreTypes.asDeclared(typeElement.asType()), types)
.build();
}
private MethodSpec privateConstructor() {
return constructorBuilder().addModifiers(PRIVATE).build();
}
/**
* Returns the {@link ComponentRequirement}s for a component that does not have a {@link
* ComponentDescriptor#creatorDescriptor()}.
*/
private Stream<ComponentRequirement> componentRequirements(ComponentDescriptor component) {
checkArgument(component.kind().isRoot());
return Stream.concat(
component.dependencies().stream(),
component.modules().stream()
.filter(module -> !module.moduleElement().getModifiers().contains(ABSTRACT))
.map(module -> ComponentRequirement.forModule(module.moduleElement().asType())));
}
private boolean hasBindsInstanceMethods(ComponentDescriptor componentDescriptor) {
return componentDescriptor.creatorDescriptor().isPresent()
&& methodsIn(
elements.getAllMembers(componentDescriptor.creatorDescriptor().get().typeElement()))
.stream()
.anyMatch(method -> isAnnotationPresent(method, BindsInstance.class));
}
private MethodSpec builderInstanceMethod(
TypeElement componentRequirement, ClassName builderClass) {
String simpleName =
UPPER_CAMEL.to(LOWER_CAMEL, componentRequirement.getSimpleName().toString());
return MethodSpec.methodBuilder(simpleName)
.addModifiers(PUBLIC)
.addParameter(ClassName.get(componentRequirement), simpleName)
.returns(builderClass)
.build();
}
private MethodSpec builderBuildMethod(ComponentDescriptor component) {
return MethodSpec.methodBuilder("build")
.addModifiers(PUBLIC)
.returns(ClassName.get(component.typeElement()))
.build();
}
private MethodSpec staticBuilderMethod(TypeName builderMethodReturnType) {
return MethodSpec.methodBuilder("builder")
.addModifiers(PUBLIC, STATIC)
.returns(builderMethodReturnType)
.build();
}
private MethodSpec createMethod(ComponentDescriptor componentDescriptor) {
return MethodSpec.methodBuilder("create")
.addModifiers(PUBLIC, STATIC)
.returns(ClassName.get(componentDescriptor.typeElement()))
.build();
}
private MethodSpec onProducerFutureCancelledMethod() {
return MethodSpec.methodBuilder("onProducerFutureCancelled")
.addModifiers(PUBLIC)
.addParameter(TypeName.BOOLEAN, "mayInterruptIfRunning")
.build();
}
}