blob: b3322e5b23e0561ee5a201af1d7ed8159bc52ab0 [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.validation;
import static com.google.auto.common.MoreElements.asType;
import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static com.google.auto.common.MoreTypes.asDeclared;
import static com.google.auto.common.MoreTypes.asExecutable;
import static com.google.auto.common.MoreTypes.asTypeElement;
import static com.google.common.base.Verify.verify;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Multimaps.asMap;
import static com.google.common.collect.Sets.intersection;
import static dagger.internal.codegen.base.ComponentAnnotation.anyComponentAnnotation;
import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation;
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.creatorAnnotationsFor;
import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.productionCreatorAnnotations;
import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.subcomponentCreatorAnnotations;
import static dagger.internal.codegen.binding.ComponentKind.annotationsFor;
import static dagger.internal.codegen.binding.ConfigurationAnnotations.enclosedAnnotatedTypes;
import static dagger.internal.codegen.binding.ConfigurationAnnotations.getTransitiveModules;
import static dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages.builderMethodRequiresNoArgs;
import static dagger.internal.codegen.binding.ErrorMessages.ComponentCreatorMessages.moreThanOneRefToSubcomponent;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror;
import static dagger.internal.codegen.langmodel.DaggerElements.getAnyAnnotation;
import static java.util.Comparator.comparing;
import static javax.lang.model.element.ElementKind.CLASS;
import static javax.lang.model.element.ElementKind.INTERFACE;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.type.TypeKind.VOID;
import static javax.lang.model.util.ElementFilter.methodsIn;
import com.google.auto.common.MoreTypes;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import dagger.Component;
import dagger.Reusable;
import dagger.internal.codegen.base.ClearableCache;
import dagger.internal.codegen.base.ComponentAnnotation;
import dagger.internal.codegen.binding.ComponentKind;
import dagger.internal.codegen.binding.DependencyRequestFactory;
import dagger.internal.codegen.binding.ErrorMessages;
import dagger.internal.codegen.binding.MethodSignatureFormatter;
import dagger.internal.codegen.binding.ModuleKind;
import dagger.internal.codegen.langmodel.DaggerElements;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.model.DependencyRequest;
import dagger.model.Key;
import dagger.producers.CancellationPolicy;
import dagger.producers.ProductionComponent;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVisitor;
import javax.lang.model.util.SimpleTypeVisitor8;
/**
* Performs superficial validation of the contract of the {@link Component} and {@link
* ProductionComponent} annotations.
*/
@Singleton
public final class ComponentValidator implements ClearableCache {
private final DaggerElements elements;
private final DaggerTypes types;
private final ModuleValidator moduleValidator;
private final ComponentCreatorValidator creatorValidator;
private final DependencyRequestValidator dependencyRequestValidator;
private final MembersInjectionValidator membersInjectionValidator;
private final MethodSignatureFormatter methodSignatureFormatter;
private final DependencyRequestFactory dependencyRequestFactory;
private final Map<TypeElement, ValidationReport<TypeElement>> reports = new HashMap<>();
@Inject
ComponentValidator(
DaggerElements elements,
DaggerTypes types,
ModuleValidator moduleValidator,
ComponentCreatorValidator creatorValidator,
DependencyRequestValidator dependencyRequestValidator,
MembersInjectionValidator membersInjectionValidator,
MethodSignatureFormatter methodSignatureFormatter,
DependencyRequestFactory dependencyRequestFactory) {
this.elements = elements;
this.types = types;
this.moduleValidator = moduleValidator;
this.creatorValidator = creatorValidator;
this.dependencyRequestValidator = dependencyRequestValidator;
this.membersInjectionValidator = membersInjectionValidator;
this.methodSignatureFormatter = methodSignatureFormatter;
this.dependencyRequestFactory = dependencyRequestFactory;
}
@Override
public void clearCache() {
reports.clear();
}
/** Validates the given component. */
public ValidationReport<TypeElement> validate(TypeElement component) {
return reentrantComputeIfAbsent(reports, component, this::validateUncached);
}
private ValidationReport<TypeElement> validateUncached(TypeElement component) {
return new ElementValidator(component).validateElement();
}
private class ElementValidator {
private final TypeElement component;
private final ValidationReport.Builder<TypeElement> report;
private final ImmutableSet<ComponentKind> componentKinds;
// Populated by ComponentMethodValidators
private final SetMultimap<Element, ExecutableElement> referencedSubcomponents =
LinkedHashMultimap.create();
ElementValidator(TypeElement component) {
this.component = component;
this.report = ValidationReport.about(component);
this.componentKinds = ComponentKind.getComponentKinds(component);
}
private ComponentKind componentKind() {
return getOnlyElement(componentKinds);
}
private ComponentAnnotation componentAnnotation() {
return anyComponentAnnotation(component).get();
}
private DeclaredType componentType() {
return asDeclared(component.asType());
}
ValidationReport<TypeElement> validateElement() {
if (componentKinds.size() > 1) {
return moreThanOneComponentAnnotation();
}
validateUseOfCancellationPolicy();
validateIsAbstractType();
validateCreators();
validateNoReusableAnnotation();
validateComponentMethods();
validateNoConflictingEntryPoints();
validateSubcomponentReferences();
validateComponentDependencies();
validateReferencedModules();
validateSubcomponents();
return report.build();
}
private ValidationReport<TypeElement> moreThanOneComponentAnnotation() {
String error =
"Components may not be annotated with more than one component annotation: found "
+ annotationsFor(componentKinds);
report.addError(error, component);
return report.build();
}
private void validateUseOfCancellationPolicy() {
if (isAnnotationPresent(component, CancellationPolicy.class)
&& !componentKind().isProducer()) {
report.addError(
"@CancellationPolicy may only be applied to production components and subcomponents",
component);
}
}
private void validateIsAbstractType() {
if (!component.getKind().equals(INTERFACE)
&& !(component.getKind().equals(CLASS) && component.getModifiers().contains(ABSTRACT))) {
report.addError(
String.format(
"@%s may only be applied to an interface or abstract class",
componentKind().annotation().getSimpleName()),
component);
}
}
private void validateCreators() {
ImmutableList<DeclaredType> creators =
creatorAnnotationsFor(componentAnnotation()).stream()
.flatMap(annotation -> enclosedAnnotatedTypes(component, annotation).stream())
.collect(toImmutableList());
creators.forEach(
creator -> report.addSubreport(creatorValidator.validate(asTypeElement(creator))));
if (creators.size() > 1) {
report.addError(
String.format(
ErrorMessages.componentMessagesFor(componentKind()).moreThanOne(), creators),
component);
}
}
private void validateNoReusableAnnotation() {
Optional<AnnotationMirror> reusableAnnotation =
getAnnotationMirror(component, Reusable.class);
if (reusableAnnotation.isPresent()) {
report.addError(
"@Reusable cannot be applied to components or subcomponents",
component,
reusableAnnotation.get());
}
}
private void validateComponentMethods() {
getLocalAndInheritedMethods(component, types, elements).stream()
.filter(method -> method.getModifiers().contains(ABSTRACT))
.map(ComponentMethodValidator::new)
.forEachOrdered(ComponentMethodValidator::validateMethod);
}
private class ComponentMethodValidator {
private final ExecutableElement method;
private final ExecutableType resolvedMethod;
private final List<? extends TypeMirror> parameterTypes;
private final List<? extends VariableElement> parameters;
private final TypeMirror returnType;
ComponentMethodValidator(ExecutableElement method) {
this.method = method;
this.resolvedMethod = asExecutable(types.asMemberOf(componentType(), method));
this.parameterTypes = resolvedMethod.getParameterTypes();
this.parameters = method.getParameters();
this.returnType = resolvedMethod.getReturnType();
}
void validateMethod() {
validateNoTypeVariables();
// abstract methods are ones we have to implement, so they each need to be validated
// first, check the return type. if it's a subcomponent, validate that method as
// such.
Optional<AnnotationMirror> subcomponentAnnotation = subcomponentAnnotation();
if (subcomponentAnnotation.isPresent()) {
validateSubcomponentFactoryMethod(subcomponentAnnotation.get());
} else if (subcomponentCreatorAnnotation().isPresent()) {
validateSubcomponentCreatorMethod();
} else {
// if it's not a subcomponent...
switch (parameters.size()) {
case 0:
validateProvisionMethod();
break;
case 1:
validateMembersInjectionMethod();
break;
default:
reportInvalidMethod();
break;
}
}
}
private void validateNoTypeVariables() {
if (!resolvedMethod.getTypeVariables().isEmpty()) {
report.addError("Component methods cannot have type variables", method);
}
}
private Optional<AnnotationMirror> subcomponentAnnotation() {
return checkForAnnotations(
returnType,
componentKind().legalSubcomponentKinds().stream()
.map(ComponentKind::annotation)
.collect(toImmutableSet()));
}
private Optional<AnnotationMirror> subcomponentCreatorAnnotation() {
return checkForAnnotations(
returnType,
componentAnnotation().isProduction()
? intersection(subcomponentCreatorAnnotations(), productionCreatorAnnotations())
: subcomponentCreatorAnnotations());
}
private void validateSubcomponentFactoryMethod(AnnotationMirror subcomponentAnnotation) {
referencedSubcomponents.put(MoreTypes.asElement(returnType), method);
ComponentKind subcomponentKind =
ComponentKind.forAnnotatedElement(MoreTypes.asTypeElement(returnType)).get();
ImmutableSet<TypeElement> moduleTypes =
ComponentAnnotation.componentAnnotation(subcomponentAnnotation).modules();
// TODO(gak): This logic maybe/probably shouldn't live here as it requires us to traverse
// subcomponents and their modules separately from how it is done in ComponentDescriptor and
// ModuleDescriptor
@SuppressWarnings("deprecation")
ImmutableSet<TypeElement> transitiveModules =
getTransitiveModules(types, elements, moduleTypes);
Set<TypeElement> variableTypes = Sets.newHashSet();
for (int i = 0; i < parameterTypes.size(); i++) {
VariableElement parameter = parameters.get(i);
TypeMirror parameterType = parameterTypes.get(i);
Optional<TypeElement> moduleType =
parameterType.accept(
new SimpleTypeVisitor8<Optional<TypeElement>, Void>() {
@Override
protected Optional<TypeElement> defaultAction(TypeMirror e, Void p) {
return Optional.empty();
}
@Override
public Optional<TypeElement> visitDeclared(DeclaredType t, Void p) {
for (ModuleKind moduleKind : subcomponentKind.legalModuleKinds()) {
if (isAnnotationPresent(t.asElement(), moduleKind.annotation())) {
return Optional.of(MoreTypes.asTypeElement(t));
}
}
return Optional.empty();
}
},
null);
if (moduleType.isPresent()) {
if (variableTypes.contains(moduleType.get())) {
report.addError(
String.format(
"A module may only occur once an an argument in a Subcomponent factory "
+ "method, but %s was already passed.",
moduleType.get().getQualifiedName()),
parameter);
}
if (!transitiveModules.contains(moduleType.get())) {
report.addError(
String.format(
"%s is present as an argument to the %s factory method, but is not one of the"
+ " modules used to implement the subcomponent.",
moduleType.get().getQualifiedName(),
MoreTypes.asTypeElement(returnType).getQualifiedName()),
method);
}
variableTypes.add(moduleType.get());
} else {
report.addError(
String.format(
"Subcomponent factory methods may only accept modules, but %s is not.",
parameterType),
parameter);
}
}
}
private void validateSubcomponentCreatorMethod() {
referencedSubcomponents.put(MoreTypes.asElement(returnType).getEnclosingElement(), method);
if (!parameters.isEmpty()) {
report.addError(builderMethodRequiresNoArgs(), method);
}
TypeElement creatorElement = MoreTypes.asTypeElement(returnType);
// TODO(sameb): The creator validator right now assumes the element is being compiled
// in this pass, which isn't true here. We should change error messages to spit out
// this method as the subject and add the original subject to the message output.
report.addSubreport(creatorValidator.validate(creatorElement));
}
private void validateProvisionMethod() {
dependencyRequestValidator.validateDependencyRequest(report, method, returnType);
}
private void validateMembersInjectionMethod() {
TypeMirror parameterType = getOnlyElement(parameterTypes);
report.addSubreport(
membersInjectionValidator.validateMembersInjectionMethod(method, parameterType));
if (!(returnType.getKind().equals(VOID) || types.isSameType(returnType, parameterType))) {
report.addError(
"Members injection methods may only return the injected type or void.", method);
}
}
private void reportInvalidMethod() {
report.addError(
"This method isn't a valid provision method, members injection method or "
+ "subcomponent factory method. Dagger cannot implement this method",
method);
}
}
private void validateNoConflictingEntryPoints() {
// Collect entry point methods that are not overridden by others. If the "same" method is
// inherited from more than one supertype, each will be in the multimap.
SetMultimap<String, ExecutableElement> entryPointMethods = HashMultimap.create();
methodsIn(elements.getAllMembers(component)).stream()
.filter(
method ->
isEntryPoint(method, asExecutable(types.asMemberOf(componentType(), method))))
.forEach(
method ->
addMethodUnlessOverridden(
method, entryPointMethods.get(method.getSimpleName().toString())));
for (Set<ExecutableElement> methods : asMap(entryPointMethods).values()) {
if (distinctKeys(methods).size() > 1) {
reportConflictingEntryPoints(methods);
}
}
}
private void reportConflictingEntryPoints(Collection<ExecutableElement> methods) {
verify(
methods.stream().map(ExecutableElement::getEnclosingElement).distinct().count()
== methods.size(),
"expected each method to be declared on a different type: %s",
methods);
StringBuilder message = new StringBuilder("conflicting entry point declarations:");
methodSignatureFormatter
.typedFormatter(componentType())
.formatIndentedList(
message,
ImmutableList.sortedCopyOf(
comparing(
method -> asType(method.getEnclosingElement()).getQualifiedName().toString()),
methods),
1);
report.addError(message.toString());
}
private void validateSubcomponentReferences() {
Maps.filterValues(referencedSubcomponents.asMap(), methods -> methods.size() > 1)
.forEach(
(subcomponent, methods) ->
report.addError(
String.format(moreThanOneRefToSubcomponent(), subcomponent, methods),
component));
}
private void validateComponentDependencies() {
for (TypeMirror type : componentAnnotation().dependencyTypes()) {
type.accept(CHECK_DEPENDENCY_TYPES, report);
}
}
private void validateReferencedModules() {
report.addSubreport(
moduleValidator.validateReferencedModules(
component,
componentAnnotation().annotation(),
componentKind().legalModuleKinds(),
new HashSet<>()));
}
private void validateSubcomponents() {
// Make sure we validate any subcomponents we're referencing.
for (Element subcomponent : referencedSubcomponents.keySet()) {
ValidationReport<TypeElement> subreport = validate(asType(subcomponent));
report.addSubreport(subreport);
}
}
private ImmutableSet<Key> distinctKeys(Set<ExecutableElement> methods) {
return methods.stream()
.map(this::dependencyRequest)
.map(DependencyRequest::key)
.collect(toImmutableSet());
}
private DependencyRequest dependencyRequest(ExecutableElement method) {
ExecutableType methodType = asExecutable(types.asMemberOf(componentType(), method));
return ComponentKind.forAnnotatedElement(component).get().isProducer()
? dependencyRequestFactory.forComponentProductionMethod(method, methodType)
: dependencyRequestFactory.forComponentProvisionMethod(method, methodType);
}
}
private static boolean isEntryPoint(ExecutableElement method, ExecutableType methodType) {
return method.getModifiers().contains(ABSTRACT)
&& method.getParameters().isEmpty()
&& !methodType.getReturnType().getKind().equals(VOID)
&& methodType.getTypeVariables().isEmpty();
}
private void addMethodUnlessOverridden(ExecutableElement method, Set<ExecutableElement> methods) {
if (methods.stream().noneMatch(existingMethod -> overridesAsDeclared(existingMethod, method))) {
methods.removeIf(existingMethod -> overridesAsDeclared(method, existingMethod));
methods.add(method);
}
}
/**
* Returns {@code true} if {@code overrider} overrides {@code overridden} considered from within
* the type that declares {@code overrider}.
*/
// TODO(dpb): Does this break for ECJ?
private boolean overridesAsDeclared(ExecutableElement overrider, ExecutableElement overridden) {
return elements.overrides(overrider, overridden, asType(overrider.getEnclosingElement()));
}
private static final TypeVisitor<Void, ValidationReport.Builder<?>> CHECK_DEPENDENCY_TYPES =
new SimpleTypeVisitor8<Void, ValidationReport.Builder<?>>() {
@Override
protected Void defaultAction(TypeMirror type, ValidationReport.Builder<?> report) {
report.addError(type + " is not a valid component dependency type");
return null;
}
@Override
public Void visitDeclared(DeclaredType type, ValidationReport.Builder<?> report) {
if (moduleAnnotation(MoreTypes.asTypeElement(type)).isPresent()) {
report.addError(type + " is a module, which cannot be a component dependency");
}
return null;
}
};
private static Optional<AnnotationMirror> checkForAnnotations(
TypeMirror type, final Set<? extends Class<? extends Annotation>> annotations) {
return type.accept(
new SimpleTypeVisitor8<Optional<AnnotationMirror>, Void>(Optional.empty()) {
@Override
public Optional<AnnotationMirror> visitDeclared(DeclaredType t, Void p) {
return getAnyAnnotation(t.asElement(), annotations);
}
},
null);
}
}