117 lines
4.8 KiB
Java
117 lines
4.8 KiB
Java
/*
|
|
* 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 androidx.room.compiler.processing.XTypeKt.isArray;
|
|
import static com.google.common.base.Preconditions.checkArgument;
|
|
import static dagger.internal.codegen.xprocessing.XTypes.asArray;
|
|
import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
|
|
import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive;
|
|
import static dagger.internal.codegen.xprocessing.XTypes.isRawParameterizedType;
|
|
|
|
import androidx.room.compiler.processing.XAnnotation;
|
|
import androidx.room.compiler.processing.XElement;
|
|
import androidx.room.compiler.processing.XMethodElement;
|
|
import androidx.room.compiler.processing.XType;
|
|
import dagger.internal.codegen.binding.InjectionAnnotations;
|
|
import javax.inject.Inject;
|
|
|
|
/**
|
|
* Validates members injection requests (members injection methods on components and requests for
|
|
* {@code MembersInjector<Foo>}).
|
|
*/
|
|
final class MembersInjectionValidator {
|
|
private final InjectionAnnotations injectionAnnotations;
|
|
|
|
@Inject
|
|
MembersInjectionValidator(InjectionAnnotations injectionAnnotations) {
|
|
this.injectionAnnotations = injectionAnnotations;
|
|
}
|
|
|
|
/** Reports errors if a request for a {@code MembersInjector<Foo>}) is invalid. */
|
|
ValidationReport validateMembersInjectionRequest(
|
|
XElement requestElement, XType membersInjectedType) {
|
|
ValidationReport.Builder report = ValidationReport.about(requestElement);
|
|
checkQualifiers(report, requestElement);
|
|
checkMembersInjectedType(report, membersInjectedType);
|
|
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(
|
|
XMethodElement method, XType 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));
|
|
checkMembersInjectedType(report, membersInjectedType);
|
|
return report.build();
|
|
}
|
|
|
|
private void checkQualifiers(ValidationReport.Builder report, XElement element) {
|
|
for (XAnnotation 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 void checkMembersInjectedType(ValidationReport.Builder report, XType type) {
|
|
// Only declared types can be members-injected.
|
|
if (!isDeclared(type)) {
|
|
report.addError("Cannot inject members into " + type);
|
|
return;
|
|
}
|
|
|
|
// If the type is the erasure of a generic type, that means the user referred to
|
|
// Foo<T> 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 (isRawParameterizedType(type)) {
|
|
report.addError("Cannot inject members into raw type " + type);
|
|
return;
|
|
}
|
|
|
|
// 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.
|
|
if (!type.getTypeArguments().stream().allMatch(this::isResolvableTypeArgument)) {
|
|
report.addError("Cannot inject members into types with unbounded type arguments: " + type);
|
|
}
|
|
}
|
|
|
|
// TODO(dpb): Can this be inverted so it explicitly rejects wildcards or type variables?
|
|
// This logic is hard to describe.
|
|
private boolean isResolvableTypeArgument(XType type) {
|
|
return isDeclared(type)
|
|
|| (isArray(type) && isResolvableArrayComponentType(asArray(type).getComponentType()));
|
|
}
|
|
|
|
private boolean isResolvableArrayComponentType(XType type) {
|
|
if (isDeclared(type)) {
|
|
return type.getTypeArguments().stream().allMatch(this::isResolvableTypeArgument);
|
|
} else if (isArray(type)) {
|
|
return isResolvableArrayComponentType(asArray(type).getComponentType());
|
|
}
|
|
return isPrimitive(type);
|
|
}
|
|
}
|