/* * Copyright (C) 2018 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.common.base.Preconditions.checkArgument; import com.google.auto.common.MoreElements; import dagger.internal.codegen.binding.InjectionAnnotations; import javax.inject.Inject; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVisitor; import javax.lang.model.util.SimpleTypeVisitor8; /** * Validates members injection requests (members injection methods on components and requests for * {@code MembersInjector}). */ final class MembersInjectionValidator { private final InjectionAnnotations injectionAnnotations; @Inject MembersInjectionValidator(InjectionAnnotations injectionAnnotations) { this.injectionAnnotations = injectionAnnotations; } /** Reports errors if a request for a {@code MembersInjector}) is invalid. */ ValidationReport validateMembersInjectionRequest( Element requestElement, TypeMirror membersInjectedType) { ValidationReport.Builder report = ValidationReport.about(requestElement); checkQualifiers(report, requestElement); membersInjectedType.accept(VALIDATE_MEMBERS_INJECTED_TYPE, report); return report.build(); } /** * Reports errors if a members injection method on a component is invalid. * * @throws IllegalArgumentException if the method doesn't have exactly one parameter */ ValidationReport validateMembersInjectionMethod( ExecutableElement method, TypeMirror membersInjectedType) { checkArgument( method.getParameters().size() == 1, "expected a method with one parameter: %s", method); ValidationReport.Builder report = ValidationReport.about(method); checkQualifiers(report, method); checkQualifiers(report, method.getParameters().get(0)); membersInjectedType.accept(VALIDATE_MEMBERS_INJECTED_TYPE, report); return report.build(); } private void checkQualifiers(ValidationReport.Builder report, Element element) { for (AnnotationMirror qualifier : injectionAnnotations.getQualifiers(element)) { report.addError("Cannot inject members into qualified types", element, qualifier); break; // just report on the first qualifier, in case there is more than one } } private static final TypeVisitor> VALIDATE_MEMBERS_INJECTED_TYPE = new SimpleTypeVisitor8>() { // Only declared types can be members-injected. @Override protected Void defaultAction(TypeMirror type, ValidationReport.Builder report) { report.addError("Cannot inject members into " + type); return null; } @Override public Void visitDeclared(DeclaredType type, ValidationReport.Builder report) { if (type.getTypeArguments().isEmpty()) { // If the type is the erasure of a generic type, that means the user referred to // Foo as just 'Foo', which we don't allow. (This is a judgement call; we // *could* allow it and instantiate the type bounds, but we don't.) if (!MoreElements.asType(type.asElement()).getTypeParameters().isEmpty()) { report.addError("Cannot inject members into raw type " + type); } } else { // If the type has arguments, validate that each type argument is declared. // Otherwise the type argument may be a wildcard (or other type), and we can't // resolve that to actual types. For array type arguments, validate the type of the // array. for (TypeMirror arg : type.getTypeArguments()) { if (!arg.accept(DECLARED_OR_ARRAY, null)) { report.addError( "Cannot inject members into types with unbounded type arguments: " + type); } } } return null; } }; // TODO(dpb): Can this be inverted so it explicitly rejects wildcards or type variables? // This logic is hard to describe. private static final TypeVisitor DECLARED_OR_ARRAY = new SimpleTypeVisitor8(false) { @Override public Boolean visitArray(ArrayType arrayType, Void p) { return arrayType .getComponentType() .accept( new SimpleTypeVisitor8(false) { @Override public Boolean visitDeclared(DeclaredType declaredType, Void p) { for (TypeMirror arg : declaredType.getTypeArguments()) { if (!arg.accept(this, null)) { return false; } } return true; } @Override public Boolean visitArray(ArrayType arrayType, Void p) { return arrayType.getComponentType().accept(this, null); } @Override public Boolean visitPrimitive(PrimitiveType primitiveType, Void p) { return true; } }, null); } @Override public Boolean visitDeclared(DeclaredType t, Void p) { return true; } }; }