blob: 81349b9fb0b54dd9de0be6f05584faae197fac05 [file] [log] [blame]
/*
* Copyright (C) 2016 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 dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent;
import static java.util.stream.Collectors.joining;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.PRIVATE;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.FormatMethod;
import dagger.internal.codegen.binding.InjectionAnnotations;
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
import dagger.internal.codegen.langmodel.DaggerElements;
import dagger.internal.codegen.langmodel.DaggerTypes;
import java.lang.annotation.Annotation;
import java.util.Optional;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
/** A validator for methods that represent binding declarations. */
abstract class BindingMethodValidator extends BindingElementValidator<ExecutableElement> {
private final DaggerElements elements;
private final DaggerTypes types;
private final KotlinMetadataUtil metadataUtil;
private final DependencyRequestValidator dependencyRequestValidator;
private final Class<? extends Annotation> methodAnnotation;
private final ImmutableSet<? extends Class<? extends Annotation>> enclosingElementAnnotations;
private final Abstractness abstractness;
private final ExceptionSuperclass exceptionSuperclass;
/**
* Creates a validator object.
*
* @param methodAnnotation the annotation on a method that identifies it as a binding method
* @param enclosingElementAnnotation the method must be declared in a class or interface annotated
* with this annotation
*/
protected BindingMethodValidator(
DaggerElements elements,
DaggerTypes types,
KotlinMetadataUtil metadataUtil,
DependencyRequestValidator dependencyRequestValidator,
Class<? extends Annotation> methodAnnotation,
Class<? extends Annotation> enclosingElementAnnotation,
Abstractness abstractness,
ExceptionSuperclass exceptionSuperclass,
AllowsMultibindings allowsMultibindings,
AllowsScoping allowsScoping,
InjectionAnnotations injectionAnnotations) {
this(
elements,
types,
metadataUtil,
methodAnnotation,
ImmutableSet.of(enclosingElementAnnotation),
dependencyRequestValidator,
abstractness,
exceptionSuperclass,
allowsMultibindings,
allowsScoping,
injectionAnnotations);
}
/**
* Creates a validator object.
*
* @param methodAnnotation the annotation on a method that identifies it as a binding method
* @param enclosingElementAnnotations the method must be declared in a class or interface
* annotated with one of these annotations
*/
protected BindingMethodValidator(
DaggerElements elements,
DaggerTypes types,
KotlinMetadataUtil metadataUtil,
Class<? extends Annotation> methodAnnotation,
Iterable<? extends Class<? extends Annotation>> enclosingElementAnnotations,
DependencyRequestValidator dependencyRequestValidator,
Abstractness abstractness,
ExceptionSuperclass exceptionSuperclass,
AllowsMultibindings allowsMultibindings,
AllowsScoping allowsScoping,
InjectionAnnotations injectionAnnotations) {
super(methodAnnotation, allowsMultibindings, allowsScoping, injectionAnnotations);
this.elements = elements;
this.types = types;
this.metadataUtil = metadataUtil;
this.methodAnnotation = methodAnnotation;
this.enclosingElementAnnotations = ImmutableSet.copyOf(enclosingElementAnnotations);
this.dependencyRequestValidator = dependencyRequestValidator;
this.abstractness = abstractness;
this.exceptionSuperclass = exceptionSuperclass;
}
/** The annotation that identifies binding methods validated by this object. */
final Class<? extends Annotation> methodAnnotation() {
return methodAnnotation;
}
/**
* Returns an error message of the form "@<i>annotation</i> methods <i>rule</i>", where
* <i>rule</i> comes from calling {@link String#format(String, Object...)} on {@code ruleFormat}
* and the other arguments.
*/
@FormatMethod
protected final String bindingMethods(String ruleFormat, Object... args) {
return bindingElements(ruleFormat, args);
}
@Override
protected final String bindingElements() {
return String.format("@%s methods", methodAnnotation.getSimpleName());
}
@Override
protected final String bindingElementTypeVerb() {
return "return";
}
/** Abstract validator for individual binding method elements. */
protected abstract class MethodValidator extends ElementValidator {
protected MethodValidator(ExecutableElement element) {
super(element);
}
@Override
protected final Optional<TypeMirror> bindingElementType() {
return Optional.of(element.getReturnType());
}
@Override
protected final void checkAdditionalProperties() {
checkEnclosingElement();
checkTypeParameters();
checkNotPrivate();
checkAbstractness();
checkThrows();
checkParameters();
checkAdditionalMethodProperties();
}
/** Checks additional properties of the binding method. */
protected void checkAdditionalMethodProperties() {}
/**
* Adds an error if the method is not declared in a class or interface annotated with one of the
* {@link #enclosingElementAnnotations}.
*/
private void checkEnclosingElement() {
TypeElement enclosingElement = asType(element.getEnclosingElement());
if (metadataUtil.isCompanionObjectClass(enclosingElement)) {
// Binding method is in companion object, use companion object's enclosing class instead.
enclosingElement = asType(enclosingElement.getEnclosingElement());
}
if (!isAnyAnnotationPresent(enclosingElement, enclosingElementAnnotations)) {
report.addError(
bindingMethods(
"can only be present within a @%s",
enclosingElementAnnotations.stream()
.map(Class::getSimpleName)
.collect(joining(" or @"))));
}
}
/** Adds an error if the method is generic. */
private void checkTypeParameters() {
if (!element.getTypeParameters().isEmpty()) {
report.addError(bindingMethods("may not have type parameters"));
}
}
/** Adds an error if the method is private. */
private void checkNotPrivate() {
if (element.getModifiers().contains(PRIVATE)) {
report.addError(bindingMethods("cannot be private"));
}
}
/** Adds an error if the method is abstract but must not be, or is not and must be. */
private void checkAbstractness() {
boolean isAbstract = element.getModifiers().contains(ABSTRACT);
switch (abstractness) {
case MUST_BE_ABSTRACT:
if (!isAbstract) {
report.addError(bindingMethods("must be abstract"));
}
break;
case MUST_BE_CONCRETE:
if (isAbstract) {
report.addError(bindingMethods("cannot be abstract"));
}
}
}
/**
* Adds an error if the method declares throws anything but an {@link Error} or an appropriate
* subtype of {@link Exception}.
*/
private void checkThrows() {
exceptionSuperclass.checkThrows(BindingMethodValidator.this, element, report);
}
/** Adds errors for the method parameters. */
protected void checkParameters() {
for (VariableElement parameter : element.getParameters()) {
checkParameter(parameter);
}
}
/**
* Adds errors for a method parameter. This implementation reports an error if the parameter has
* more than one qualifier.
*/
protected void checkParameter(VariableElement parameter) {
dependencyRequestValidator.validateDependencyRequest(report, parameter, parameter.asType());
}
}
/** An abstract/concrete restriction on methods. */
protected enum Abstractness {
MUST_BE_ABSTRACT,
MUST_BE_CONCRETE
}
/**
* The exception class that all {@code throws}-declared throwables must extend, other than {@link
* Error}.
*/
protected enum ExceptionSuperclass {
/** Methods may not declare any throwable types. */
NO_EXCEPTIONS {
@Override
protected String errorMessage(BindingMethodValidator validator) {
return validator.bindingMethods("may not throw");
}
@Override
protected void checkThrows(
BindingMethodValidator validator,
ExecutableElement element,
ValidationReport.Builder<ExecutableElement> report) {
if (!element.getThrownTypes().isEmpty()) {
report.addError(validator.bindingMethods("may not throw"));
return;
}
}
},
/** Methods may throw checked or unchecked exceptions or errors. */
EXCEPTION(Exception.class) {
@Override
protected String errorMessage(BindingMethodValidator validator) {
return validator.bindingMethods(
"may only throw unchecked exceptions or exceptions subclassing Exception");
}
},
/** Methods may throw unchecked exceptions or errors. */
RUNTIME_EXCEPTION(RuntimeException.class) {
@Override
protected String errorMessage(BindingMethodValidator validator) {
return validator.bindingMethods("may only throw unchecked exceptions");
}
},
;
private final Class<? extends Exception> superclass;
ExceptionSuperclass() {
this(null);
}
ExceptionSuperclass(Class<? extends Exception> superclass) {
this.superclass = superclass;
}
/**
* Adds an error if the method declares throws anything but an {@link Error} or an appropriate
* subtype of {@link Exception}.
*
* <p>This method is overridden in {@link #NO_EXCEPTIONS}.
*/
protected void checkThrows(
BindingMethodValidator validator,
ExecutableElement element,
ValidationReport.Builder<ExecutableElement> report) {
TypeMirror exceptionSupertype = validator.elements.getTypeElement(superclass).asType();
TypeMirror errorType = validator.elements.getTypeElement(Error.class).asType();
for (TypeMirror thrownType : element.getThrownTypes()) {
if (!validator.types.isSubtype(thrownType, exceptionSupertype)
&& !validator.types.isSubtype(thrownType, errorType)) {
report.addError(errorMessage(validator));
break;
}
}
}
protected abstract String errorMessage(BindingMethodValidator validator);
}
}