1 /* 2 * Copyright (C) 2018 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.internal.codegen.validation; 18 19 import static androidx.room.compiler.processing.XTypeKt.isArray; 20 import static com.google.common.base.Preconditions.checkArgument; 21 import static dagger.internal.codegen.xprocessing.XTypes.asArray; 22 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; 23 import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive; 24 import static dagger.internal.codegen.xprocessing.XTypes.isRawParameterizedType; 25 26 import androidx.room.compiler.processing.XAnnotation; 27 import androidx.room.compiler.processing.XElement; 28 import androidx.room.compiler.processing.XMethodElement; 29 import androidx.room.compiler.processing.XType; 30 import dagger.internal.codegen.binding.InjectionAnnotations; 31 import dagger.internal.codegen.xprocessing.XTypes; 32 import javax.inject.Inject; 33 34 /** 35 * Validates members injection requests (members injection methods on components and requests for 36 * {@code MembersInjector<Foo>}). 37 */ 38 final class MembersInjectionValidator { 39 private final InjectionAnnotations injectionAnnotations; 40 41 @Inject MembersInjectionValidator(InjectionAnnotations injectionAnnotations)42 MembersInjectionValidator(InjectionAnnotations injectionAnnotations) { 43 this.injectionAnnotations = injectionAnnotations; 44 } 45 46 /** Reports errors if a request for a {@code MembersInjector<Foo>}) is invalid. */ validateMembersInjectionRequest( XElement requestElement, XType membersInjectedType)47 ValidationReport validateMembersInjectionRequest( 48 XElement requestElement, XType membersInjectedType) { 49 ValidationReport.Builder report = ValidationReport.about(requestElement); 50 checkQualifiers(report, requestElement); 51 checkMembersInjectedType(report, membersInjectedType); 52 return report.build(); 53 } 54 55 /** 56 * Reports errors if a members injection method on a component is invalid. 57 * 58 * @throws IllegalArgumentException if the method doesn't have exactly one parameter 59 */ validateMembersInjectionMethod( XMethodElement method, XType membersInjectedType)60 ValidationReport validateMembersInjectionMethod( 61 XMethodElement method, XType membersInjectedType) { 62 checkArgument( 63 method.getParameters().size() == 1, "expected a method with one parameter: %s", method); 64 65 ValidationReport.Builder report = ValidationReport.about(method); 66 checkQualifiers(report, method); 67 checkQualifiers(report, method.getParameters().get(0)); 68 checkMembersInjectedType(report, membersInjectedType); 69 return report.build(); 70 } 71 checkQualifiers(ValidationReport.Builder report, XElement element)72 private void checkQualifiers(ValidationReport.Builder report, XElement element) { 73 for (XAnnotation qualifier : injectionAnnotations.getQualifiers(element)) { 74 report.addError("Cannot inject members into qualified types", element, qualifier); 75 break; // just report on the first qualifier, in case there is more than one 76 } 77 } 78 checkMembersInjectedType(ValidationReport.Builder report, XType type)79 private void checkMembersInjectedType(ValidationReport.Builder report, XType type) { 80 // Only declared types can be members-injected. 81 if (!isDeclared(type)) { 82 report.addError("Cannot inject members into " + XTypes.toStableString(type)); 83 return; 84 } 85 86 // If the type is the erasure of a generic type, that means the user referred to 87 // Foo<T> as just 'Foo', which we don't allow. (This is a judgement call; we 88 // *could* allow it and instantiate the type bounds, but we don't.) 89 if (isRawParameterizedType(type)) { 90 report.addError("Cannot inject members into raw type " + XTypes.toStableString(type)); 91 return; 92 } 93 94 // If the type has arguments, validate that each type argument is declared. 95 // Otherwise the type argument may be a wildcard (or other type), and we can't 96 // resolve that to actual types. For array type arguments, validate the type of the array. 97 if (!type.getTypeArguments().stream().allMatch(this::isResolvableTypeArgument)) { 98 report.addError( 99 "Cannot inject members into types with unbounded type arguments: " 100 + XTypes.toStableString(type)); 101 } 102 } 103 104 // TODO(dpb): Can this be inverted so it explicitly rejects wildcards or type variables? 105 // This logic is hard to describe. isResolvableTypeArgument(XType type)106 private boolean isResolvableTypeArgument(XType type) { 107 return isDeclared(type) 108 || (isArray(type) && isResolvableArrayComponentType(asArray(type).getComponentType())); 109 } 110 isResolvableArrayComponentType(XType type)111 private boolean isResolvableArrayComponentType(XType type) { 112 if (isDeclared(type)) { 113 return type.getTypeArguments().stream().allMatch(this::isResolvableTypeArgument); 114 } else if (isArray(type)) { 115 return isResolvableArrayComponentType(asArray(type).getComponentType()); 116 } 117 return isPrimitive(type); 118 } 119 } 120