/* * 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 { private final DaggerElements elements; private final DaggerTypes types; private final KotlinMetadataUtil metadataUtil; private final DependencyRequestValidator dependencyRequestValidator; private final Class methodAnnotation; private final ImmutableSet> 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 methodAnnotation, Class 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 methodAnnotation, Iterable> 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 methodAnnotation() { return methodAnnotation; } /** * Returns an error message of the form "@annotation methods rule", where * rule 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 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 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 superclass; ExceptionSuperclass() { this(null); } ExceptionSuperclass(Class superclass) { this.superclass = superclass; } /** * Adds an error if the method declares throws anything but an {@link Error} or an appropriate * subtype of {@link Exception}. * *

This method is overridden in {@link #NO_EXCEPTIONS}. */ protected void checkThrows( BindingMethodValidator validator, ExecutableElement element, ValidationReport.Builder 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); } }