713 lines
32 KiB
Java
713 lines
32 KiB
Java
/*
|
|
* Copyright (C) 2014 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.AnnotationMirrors.getAnnotatedAnnotations;
|
|
import static com.google.auto.common.MoreElements.isAnnotationPresent;
|
|
import static com.google.auto.common.MoreTypes.asTypeElement;
|
|
import static com.google.auto.common.Visibility.PRIVATE;
|
|
import static com.google.auto.common.Visibility.PUBLIC;
|
|
import static com.google.auto.common.Visibility.effectiveVisibilityOfElement;
|
|
import static com.google.common.base.Preconditions.checkArgument;
|
|
import static com.google.common.collect.Iterables.getOnlyElement;
|
|
import static dagger.internal.codegen.base.ComponentAnnotation.componentAnnotation;
|
|
import static dagger.internal.codegen.base.ComponentAnnotation.isComponentAnnotation;
|
|
import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation;
|
|
import static dagger.internal.codegen.base.ModuleAnnotation.isModuleAnnotation;
|
|
import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation;
|
|
import static dagger.internal.codegen.base.MoreAnnotationMirrors.simpleName;
|
|
import static dagger.internal.codegen.base.MoreAnnotationValues.asType;
|
|
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
|
|
import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.getCreatorAnnotations;
|
|
import static dagger.internal.codegen.binding.ConfigurationAnnotations.getSubcomponentCreator;
|
|
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
|
|
import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror;
|
|
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.STATIC;
|
|
import static javax.lang.model.util.ElementFilter.methodsIn;
|
|
|
|
import com.google.auto.common.MoreElements;
|
|
import com.google.auto.common.MoreTypes;
|
|
import com.google.auto.common.Visibility;
|
|
import com.google.common.base.Joiner;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableListMultimap;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.collect.ListMultimap;
|
|
import com.google.common.collect.MultimapBuilder;
|
|
import com.google.common.collect.Multimaps;
|
|
import com.google.common.collect.Sets;
|
|
import com.google.errorprone.annotations.FormatMethod;
|
|
import dagger.Module;
|
|
import dagger.Subcomponent;
|
|
import dagger.internal.codegen.base.ModuleAnnotation;
|
|
import dagger.internal.codegen.binding.BindingGraphFactory;
|
|
import dagger.internal.codegen.binding.ComponentCreatorAnnotation;
|
|
import dagger.internal.codegen.binding.ComponentDescriptorFactory;
|
|
import dagger.internal.codegen.binding.MethodSignatureFormatter;
|
|
import dagger.internal.codegen.binding.ModuleKind;
|
|
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
|
|
import dagger.internal.codegen.langmodel.DaggerElements;
|
|
import dagger.internal.codegen.langmodel.DaggerTypes;
|
|
import dagger.model.BindingGraph;
|
|
import dagger.producers.ProducerModule;
|
|
import dagger.producers.ProductionSubcomponent;
|
|
import java.lang.annotation.Annotation;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.EnumSet;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import javax.inject.Inject;
|
|
import javax.inject.Scope;
|
|
import javax.inject.Singleton;
|
|
import javax.lang.model.element.AnnotationMirror;
|
|
import javax.lang.model.element.AnnotationValue;
|
|
import javax.lang.model.element.ElementKind;
|
|
import javax.lang.model.element.ExecutableElement;
|
|
import javax.lang.model.element.Name;
|
|
import javax.lang.model.element.TypeElement;
|
|
import javax.lang.model.type.DeclaredType;
|
|
import javax.lang.model.type.TypeMirror;
|
|
import javax.lang.model.util.SimpleAnnotationValueVisitor8;
|
|
import javax.lang.model.util.SimpleTypeVisitor8;
|
|
|
|
/** A {@linkplain ValidationReport validator} for {@link Module}s or {@link ProducerModule}s. */
|
|
@Singleton
|
|
public final class ModuleValidator {
|
|
private static final ImmutableSet<Class<? extends Annotation>> SUBCOMPONENT_TYPES =
|
|
ImmutableSet.of(Subcomponent.class, ProductionSubcomponent.class);
|
|
private static final ImmutableSet<Class<? extends Annotation>> SUBCOMPONENT_CREATOR_TYPES =
|
|
ImmutableSet.of(
|
|
Subcomponent.Builder.class,
|
|
Subcomponent.Factory.class,
|
|
ProductionSubcomponent.Builder.class,
|
|
ProductionSubcomponent.Factory.class);
|
|
private static final Optional<Class<?>> ANDROID_PROCESSOR;
|
|
private static final String CONTRIBUTES_ANDROID_INJECTOR_NAME =
|
|
"dagger.android.ContributesAndroidInjector";
|
|
private static final String ANDROID_PROCESSOR_NAME = "dagger.android.processor.AndroidProcessor";
|
|
|
|
static {
|
|
Class<?> clazz;
|
|
try {
|
|
clazz = Class.forName(ANDROID_PROCESSOR_NAME, false, ModuleValidator.class.getClassLoader());
|
|
} catch (ClassNotFoundException ignored) {
|
|
clazz = null;
|
|
}
|
|
ANDROID_PROCESSOR = Optional.ofNullable(clazz);
|
|
}
|
|
|
|
private final DaggerTypes types;
|
|
private final DaggerElements elements;
|
|
private final AnyBindingMethodValidator anyBindingMethodValidator;
|
|
private final MethodSignatureFormatter methodSignatureFormatter;
|
|
private final ComponentDescriptorFactory componentDescriptorFactory;
|
|
private final BindingGraphFactory bindingGraphFactory;
|
|
private final BindingGraphValidator bindingGraphValidator;
|
|
private final KotlinMetadataUtil metadataUtil;
|
|
private final Map<TypeElement, ValidationReport<TypeElement>> cache = new HashMap<>();
|
|
private final Set<TypeElement> knownModules = new HashSet<>();
|
|
|
|
@Inject
|
|
ModuleValidator(
|
|
DaggerTypes types,
|
|
DaggerElements elements,
|
|
AnyBindingMethodValidator anyBindingMethodValidator,
|
|
MethodSignatureFormatter methodSignatureFormatter,
|
|
ComponentDescriptorFactory componentDescriptorFactory,
|
|
BindingGraphFactory bindingGraphFactory,
|
|
BindingGraphValidator bindingGraphValidator,
|
|
KotlinMetadataUtil metadataUtil) {
|
|
this.types = types;
|
|
this.elements = elements;
|
|
this.anyBindingMethodValidator = anyBindingMethodValidator;
|
|
this.methodSignatureFormatter = methodSignatureFormatter;
|
|
this.componentDescriptorFactory = componentDescriptorFactory;
|
|
this.bindingGraphFactory = bindingGraphFactory;
|
|
this.bindingGraphValidator = bindingGraphValidator;
|
|
this.metadataUtil = metadataUtil;
|
|
}
|
|
|
|
/**
|
|
* Adds {@code modules} to the set of module types that will be validated during this compilation
|
|
* step. If a component or module includes a module that is not in this set, that included module
|
|
* is assumed to be valid because it was processed in a previous compilation step. If it were
|
|
* invalid, that previous compilation step would have failed and blocked this one.
|
|
*
|
|
* <p>This logic depends on this method being called before {@linkplain #validate(TypeElement)
|
|
* validating} any module or {@linkplain #validateReferencedModules(TypeElement, AnnotationMirror,
|
|
* ImmutableSet, Set) component}.
|
|
*/
|
|
public void addKnownModules(Collection<TypeElement> modules) {
|
|
knownModules.addAll(modules);
|
|
}
|
|
|
|
/** Returns a validation report for a module type. */
|
|
public ValidationReport<TypeElement> validate(TypeElement module) {
|
|
return validate(module, new HashSet<>());
|
|
}
|
|
|
|
private ValidationReport<TypeElement> validate(
|
|
TypeElement module, Set<TypeElement> visitedModules) {
|
|
if (visitedModules.add(module)) {
|
|
return reentrantComputeIfAbsent(cache, module, m -> validateUncached(module, visitedModules));
|
|
}
|
|
return ValidationReport.about(module).build();
|
|
}
|
|
|
|
private ValidationReport<TypeElement> validateUncached(
|
|
TypeElement module, Set<TypeElement> visitedModules) {
|
|
ValidationReport.Builder<TypeElement> builder = ValidationReport.about(module);
|
|
ModuleKind moduleKind = ModuleKind.forAnnotatedElement(module).get();
|
|
TypeElement contributesAndroidInjectorElement =
|
|
elements.getTypeElement(CONTRIBUTES_ANDROID_INJECTOR_NAME);
|
|
TypeMirror contributesAndroidInjector =
|
|
contributesAndroidInjectorElement != null
|
|
? contributesAndroidInjectorElement.asType()
|
|
: null;
|
|
List<ExecutableElement> moduleMethods = methodsIn(module.getEnclosedElements());
|
|
List<ExecutableElement> bindingMethods = new ArrayList<>();
|
|
for (ExecutableElement moduleMethod : moduleMethods) {
|
|
if (anyBindingMethodValidator.isBindingMethod(moduleMethod)) {
|
|
builder.addSubreport(anyBindingMethodValidator.validate(moduleMethod));
|
|
bindingMethods.add(moduleMethod);
|
|
}
|
|
|
|
for (AnnotationMirror annotation : moduleMethod.getAnnotationMirrors()) {
|
|
if (!ANDROID_PROCESSOR.isPresent()
|
|
&& MoreTypes.equivalence()
|
|
.equivalent(contributesAndroidInjector, annotation.getAnnotationType())) {
|
|
builder.addSubreport(
|
|
ValidationReport.about(moduleMethod)
|
|
.addError(
|
|
String.format(
|
|
"@%s was used, but %s was not found on the processor path",
|
|
CONTRIBUTES_ANDROID_INJECTOR_NAME, ANDROID_PROCESSOR_NAME))
|
|
.build());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bindingMethods.stream()
|
|
.map(ModuleMethodKind::ofMethod)
|
|
.collect(toImmutableSet())
|
|
.containsAll(
|
|
EnumSet.of(ModuleMethodKind.ABSTRACT_DECLARATION, ModuleMethodKind.INSTANCE_BINDING))) {
|
|
builder.addError(
|
|
String.format(
|
|
"A @%s may not contain both non-static and abstract binding methods",
|
|
moduleKind.annotation().getSimpleName()));
|
|
}
|
|
|
|
validateModuleVisibility(module, moduleKind, builder);
|
|
|
|
ImmutableListMultimap<Name, ExecutableElement> bindingMethodsByName =
|
|
Multimaps.index(bindingMethods, ExecutableElement::getSimpleName);
|
|
|
|
validateMethodsWithSameName(builder, bindingMethodsByName);
|
|
if (module.getKind() != ElementKind.INTERFACE) {
|
|
validateBindingMethodOverrides(
|
|
module,
|
|
builder,
|
|
Multimaps.index(moduleMethods, ExecutableElement::getSimpleName),
|
|
bindingMethodsByName);
|
|
}
|
|
validateModifiers(module, builder);
|
|
validateReferencedModules(module, moduleKind, visitedModules, builder);
|
|
validateReferencedSubcomponents(module, moduleKind, builder);
|
|
validateNoScopeAnnotationsOnModuleElement(module, moduleKind, builder);
|
|
validateSelfCycles(module, builder);
|
|
if (metadataUtil.hasEnclosedCompanionObject(module)) {
|
|
validateCompanionModule(module, builder);
|
|
}
|
|
|
|
if (builder.build().isClean()
|
|
&& bindingGraphValidator.shouldDoFullBindingGraphValidation(module)) {
|
|
validateModuleBindings(module, builder);
|
|
}
|
|
|
|
return builder.build();
|
|
}
|
|
|
|
private void validateReferencedSubcomponents(
|
|
final TypeElement subject,
|
|
ModuleKind moduleKind,
|
|
final ValidationReport.Builder<TypeElement> builder) {
|
|
// TODO(ronshapiro): use validateTypesAreDeclared when it is checked in
|
|
ModuleAnnotation moduleAnnotation = moduleAnnotation(moduleKind.getModuleAnnotation(subject));
|
|
for (AnnotationValue subcomponentAttribute :
|
|
moduleAnnotation.subcomponentsAsAnnotationValues()) {
|
|
asType(subcomponentAttribute)
|
|
.accept(
|
|
new SimpleTypeVisitor8<Void, Void>() {
|
|
@Override
|
|
protected Void defaultAction(TypeMirror e, Void aVoid) {
|
|
builder.addError(
|
|
e + " is not a valid subcomponent type",
|
|
subject,
|
|
moduleAnnotation.annotation(),
|
|
subcomponentAttribute);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Void visitDeclared(DeclaredType declaredType, Void aVoid) {
|
|
TypeElement attributeType = asTypeElement(declaredType);
|
|
if (isAnyAnnotationPresent(attributeType, SUBCOMPONENT_TYPES)) {
|
|
validateSubcomponentHasBuilder(
|
|
attributeType, moduleAnnotation.annotation(), builder);
|
|
} else {
|
|
builder.addError(
|
|
isAnyAnnotationPresent(attributeType, SUBCOMPONENT_CREATOR_TYPES)
|
|
? moduleSubcomponentsIncludesCreator(attributeType)
|
|
: moduleSubcomponentsIncludesNonSubcomponent(attributeType),
|
|
subject,
|
|
moduleAnnotation.annotation(),
|
|
subcomponentAttribute);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
},
|
|
null);
|
|
}
|
|
}
|
|
|
|
private static String moduleSubcomponentsIncludesNonSubcomponent(TypeElement notSubcomponent) {
|
|
return notSubcomponent.getQualifiedName()
|
|
+ " is not a @Subcomponent or @ProductionSubcomponent";
|
|
}
|
|
|
|
private static String moduleSubcomponentsIncludesCreator(
|
|
TypeElement moduleSubcomponentsAttribute) {
|
|
TypeElement subcomponentType =
|
|
MoreElements.asType(moduleSubcomponentsAttribute.getEnclosingElement());
|
|
ComponentCreatorAnnotation creatorAnnotation =
|
|
getOnlyElement(getCreatorAnnotations(moduleSubcomponentsAttribute));
|
|
return String.format(
|
|
"%s is a @%s.%s. Did you mean to use %s?",
|
|
moduleSubcomponentsAttribute.getQualifiedName(),
|
|
subcomponentAnnotation(subcomponentType).get().simpleName(),
|
|
creatorAnnotation.creatorKind().typeName(),
|
|
subcomponentType.getQualifiedName());
|
|
}
|
|
|
|
private static void validateSubcomponentHasBuilder(
|
|
TypeElement subcomponentAttribute,
|
|
AnnotationMirror moduleAnnotation,
|
|
ValidationReport.Builder<TypeElement> builder) {
|
|
if (getSubcomponentCreator(subcomponentAttribute).isPresent()) {
|
|
return;
|
|
}
|
|
builder.addError(
|
|
moduleSubcomponentsDoesntHaveCreator(subcomponentAttribute, moduleAnnotation),
|
|
builder.getSubject(),
|
|
moduleAnnotation);
|
|
}
|
|
|
|
private static String moduleSubcomponentsDoesntHaveCreator(
|
|
TypeElement subcomponent, AnnotationMirror moduleAnnotation) {
|
|
return String.format(
|
|
"%1$s doesn't have a @%2$s.Builder or @%2$s.Factory, which is required when used with "
|
|
+ "@%3$s.subcomponents",
|
|
subcomponent.getQualifiedName(),
|
|
subcomponentAnnotation(subcomponent).get().simpleName(),
|
|
simpleName(moduleAnnotation));
|
|
}
|
|
|
|
enum ModuleMethodKind {
|
|
ABSTRACT_DECLARATION,
|
|
INSTANCE_BINDING,
|
|
STATIC_BINDING,
|
|
;
|
|
|
|
static ModuleMethodKind ofMethod(ExecutableElement moduleMethod) {
|
|
if (moduleMethod.getModifiers().contains(STATIC)) {
|
|
return STATIC_BINDING;
|
|
} else if (moduleMethod.getModifiers().contains(ABSTRACT)) {
|
|
return ABSTRACT_DECLARATION;
|
|
} else {
|
|
return INSTANCE_BINDING;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void validateModifiers(
|
|
TypeElement subject, ValidationReport.Builder<TypeElement> builder) {
|
|
// This coupled with the check for abstract modules in ComponentValidator guarantees that
|
|
// only modules without type parameters are referenced from @Component(modules={...}).
|
|
if (!subject.getTypeParameters().isEmpty() && !subject.getModifiers().contains(ABSTRACT)) {
|
|
builder.addError("Modules with type parameters must be abstract", subject);
|
|
}
|
|
}
|
|
|
|
private void validateMethodsWithSameName(
|
|
ValidationReport.Builder<TypeElement> builder,
|
|
ListMultimap<Name, ExecutableElement> bindingMethodsByName) {
|
|
for (Entry<Name, Collection<ExecutableElement>> entry :
|
|
bindingMethodsByName.asMap().entrySet()) {
|
|
if (entry.getValue().size() > 1) {
|
|
for (ExecutableElement offendingMethod : entry.getValue()) {
|
|
builder.addError(
|
|
String.format(
|
|
"Cannot have more than one binding method with the same name in a single module"),
|
|
offendingMethod);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void validateReferencedModules(
|
|
TypeElement subject,
|
|
ModuleKind moduleKind,
|
|
Set<TypeElement> visitedModules,
|
|
ValidationReport.Builder<TypeElement> builder) {
|
|
// Validate that all the modules we include are valid for inclusion.
|
|
AnnotationMirror mirror = moduleKind.getModuleAnnotation(subject);
|
|
builder.addSubreport(
|
|
validateReferencedModules(
|
|
subject, mirror, moduleKind.legalIncludedModuleKinds(), visitedModules));
|
|
}
|
|
|
|
/**
|
|
* Validates modules included in a given module or installed in a given component.
|
|
*
|
|
* <p>Checks that the referenced modules are non-generic types annotated with {@code @Module} or
|
|
* {@code @ProducerModule}.
|
|
*
|
|
* <p>If the referenced module is in the {@linkplain #addKnownModules(Collection) known modules
|
|
* set} and has errors, reports an error at that module's inclusion.
|
|
*
|
|
* @param annotatedType the annotated module or component
|
|
* @param annotation the annotation specifying the referenced modules ({@code @Component},
|
|
* {@code @ProductionComponent}, {@code @Subcomponent}, {@code @ProductionSubcomponent},
|
|
* {@code @Module}, or {@code @ProducerModule})
|
|
* @param validModuleKinds the module kinds that the annotated type is permitted to include
|
|
*/
|
|
ValidationReport<TypeElement> validateReferencedModules(
|
|
TypeElement annotatedType,
|
|
AnnotationMirror annotation,
|
|
ImmutableSet<ModuleKind> validModuleKinds,
|
|
Set<TypeElement> visitedModules) {
|
|
ValidationReport.Builder<TypeElement> subreport = ValidationReport.about(annotatedType);
|
|
ImmutableSet<? extends Class<? extends Annotation>> validModuleAnnotations =
|
|
validModuleKinds.stream().map(ModuleKind::annotation).collect(toImmutableSet());
|
|
|
|
for (AnnotationValue includedModule : getModules(annotation)) {
|
|
asType(includedModule)
|
|
.accept(
|
|
new SimpleTypeVisitor8<Void, Void>() {
|
|
@Override
|
|
protected Void defaultAction(TypeMirror mirror, Void p) {
|
|
reportError("%s is not a valid module type.", mirror);
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Void visitDeclared(DeclaredType t, Void p) {
|
|
TypeElement module = MoreElements.asType(t.asElement());
|
|
if (!t.getTypeArguments().isEmpty()) {
|
|
reportError(
|
|
"%s is listed as a module, but has type parameters",
|
|
module.getQualifiedName());
|
|
}
|
|
if (!isAnyAnnotationPresent(module, validModuleAnnotations)) {
|
|
reportError(
|
|
"%s is listed as a module, but is not annotated with %s",
|
|
module.getQualifiedName(),
|
|
(validModuleAnnotations.size() > 1 ? "one of " : "")
|
|
+ validModuleAnnotations.stream()
|
|
.map(otherClass -> "@" + otherClass.getSimpleName())
|
|
.collect(joining(", ")));
|
|
} else if (knownModules.contains(module)
|
|
&& !validate(module, visitedModules).isClean()) {
|
|
reportError("%s has errors", module.getQualifiedName());
|
|
}
|
|
if (metadataUtil.isCompanionObjectClass(module)) {
|
|
reportError(
|
|
"%s is listed as a module, but it is a companion object class. "
|
|
+ "Add @Module to the enclosing class and reference that instead.",
|
|
module.getQualifiedName());
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@FormatMethod
|
|
private void reportError(String format, Object... args) {
|
|
subreport.addError(
|
|
String.format(format, args), annotatedType, annotation, includedModule);
|
|
}
|
|
},
|
|
null);
|
|
}
|
|
return subreport.build();
|
|
}
|
|
|
|
private static ImmutableList<AnnotationValue> getModules(AnnotationMirror annotation) {
|
|
if (isModuleAnnotation(annotation)) {
|
|
return moduleAnnotation(annotation).includesAsAnnotationValues();
|
|
}
|
|
if (isComponentAnnotation(annotation)) {
|
|
return componentAnnotation(annotation).moduleValues();
|
|
}
|
|
throw new IllegalArgumentException(String.format("unsupported annotation: %s", annotation));
|
|
}
|
|
|
|
private void validateBindingMethodOverrides(
|
|
TypeElement subject,
|
|
ValidationReport.Builder<TypeElement> builder,
|
|
ImmutableListMultimap<Name, ExecutableElement> moduleMethodsByName,
|
|
ImmutableListMultimap<Name, ExecutableElement> bindingMethodsByName) {
|
|
// For every binding method, confirm it overrides nothing *and* nothing overrides it.
|
|
// Consider the following hierarchy:
|
|
// class Parent {
|
|
// @Provides Foo a() {}
|
|
// @Provides Foo b() {}
|
|
// Foo c() {}
|
|
// }
|
|
// class Child extends Parent {
|
|
// @Provides Foo a() {}
|
|
// Foo b() {}
|
|
// @Provides Foo c() {}
|
|
// }
|
|
// In each of those cases, we want to fail. "a" is clear, "b" because Child is overriding
|
|
// a binding method in Parent, and "c" because Child is defining a binding method that overrides
|
|
// Parent.
|
|
TypeElement currentClass = subject;
|
|
TypeMirror objectType = elements.getTypeElement(Object.class).asType();
|
|
// We keep track of methods that failed so we don't spam with multiple failures.
|
|
Set<ExecutableElement> failedMethods = Sets.newHashSet();
|
|
ListMultimap<Name, ExecutableElement> allMethodsByName =
|
|
MultimapBuilder.hashKeys().arrayListValues().build(moduleMethodsByName);
|
|
|
|
while (!types.isSameType(currentClass.getSuperclass(), objectType)) {
|
|
currentClass = MoreElements.asType(types.asElement(currentClass.getSuperclass()));
|
|
List<ExecutableElement> superclassMethods = methodsIn(currentClass.getEnclosedElements());
|
|
for (ExecutableElement superclassMethod : superclassMethods) {
|
|
Name name = superclassMethod.getSimpleName();
|
|
// For each method in the superclass, confirm our binding methods don't override it
|
|
for (ExecutableElement bindingMethod : bindingMethodsByName.get(name)) {
|
|
if (failedMethods.add(bindingMethod)
|
|
&& elements.overrides(bindingMethod, superclassMethod, subject)) {
|
|
builder.addError(
|
|
String.format(
|
|
"Binding methods may not override another method. Overrides: %s",
|
|
methodSignatureFormatter.format(superclassMethod)),
|
|
bindingMethod);
|
|
}
|
|
}
|
|
// For each binding method in superclass, confirm our methods don't override it.
|
|
if (anyBindingMethodValidator.isBindingMethod(superclassMethod)) {
|
|
for (ExecutableElement method : allMethodsByName.get(name)) {
|
|
if (failedMethods.add(method)
|
|
&& elements.overrides(method, superclassMethod, subject)) {
|
|
builder.addError(
|
|
String.format(
|
|
"Binding methods may not be overridden in modules. Overrides: %s",
|
|
methodSignatureFormatter.format(superclassMethod)),
|
|
method);
|
|
}
|
|
}
|
|
}
|
|
allMethodsByName.put(superclassMethod.getSimpleName(), superclassMethod);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void validateModuleVisibility(
|
|
final TypeElement moduleElement,
|
|
ModuleKind moduleKind,
|
|
final ValidationReport.Builder<?> reportBuilder) {
|
|
ModuleAnnotation moduleAnnotation =
|
|
moduleAnnotation(getAnnotationMirror(moduleElement, moduleKind.annotation()).get());
|
|
Visibility moduleVisibility = Visibility.ofElement(moduleElement);
|
|
Visibility moduleEffectiveVisibility = effectiveVisibilityOfElement(moduleElement);
|
|
if (moduleVisibility.equals(PRIVATE)) {
|
|
reportBuilder.addError("Modules cannot be private.", moduleElement);
|
|
} else if (moduleEffectiveVisibility.equals(PRIVATE)) {
|
|
reportBuilder.addError("Modules cannot be enclosed in private types.", moduleElement);
|
|
}
|
|
|
|
switch (moduleElement.getNestingKind()) {
|
|
case ANONYMOUS:
|
|
throw new IllegalStateException("Can't apply @Module to an anonymous class");
|
|
case LOCAL:
|
|
throw new IllegalStateException("Local classes shouldn't show up in the processor");
|
|
case MEMBER:
|
|
case TOP_LEVEL:
|
|
if (moduleEffectiveVisibility.equals(PUBLIC)) {
|
|
ImmutableSet<TypeElement> invalidVisibilityIncludes =
|
|
getModuleIncludesWithInvalidVisibility(moduleAnnotation);
|
|
if (!invalidVisibilityIncludes.isEmpty()) {
|
|
reportBuilder.addError(
|
|
String.format(
|
|
"This module is public, but it includes non-public (or effectively non-public) "
|
|
+ "modules (%s) that have non-static, non-abstract binding methods. Either "
|
|
+ "reduce the visibility of this module, make the included modules "
|
|
+ "public, or make all of the binding methods on the included modules "
|
|
+ "abstract or static.",
|
|
formatListForErrorMessage(invalidVisibilityIncludes.asList())),
|
|
moduleElement);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private ImmutableSet<TypeElement> getModuleIncludesWithInvalidVisibility(
|
|
ModuleAnnotation moduleAnnotation) {
|
|
return moduleAnnotation.includes().stream()
|
|
.filter(include -> !effectiveVisibilityOfElement(include).equals(PUBLIC))
|
|
.filter(this::requiresModuleInstance)
|
|
.collect(toImmutableSet());
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if a module instance is needed for any of the binding methods on the given
|
|
* {@code module}. This is the case when the module has any binding methods that are neither
|
|
* {@code abstract} nor {@code static}. Alternatively, if the module is a Kotlin Object then the
|
|
* binding methods are considered {@code static}, requiring no module instance.
|
|
*/
|
|
private boolean requiresModuleInstance(TypeElement module) {
|
|
// Note elements.getAllMembers(module) rather than module.getEnclosedElements() here: we need to
|
|
// include binding methods declared in supertypes because unlike most other validations being
|
|
// done in this class, which assume that supertype binding methods will be validated in a
|
|
// separate call to the validator since the supertype itself must be a @Module, we need to look
|
|
// at all the binding methods in the module's type hierarchy here.
|
|
boolean isKotlinObject =
|
|
metadataUtil.isObjectClass(module) || metadataUtil.isCompanionObjectClass(module);
|
|
if (isKotlinObject) {
|
|
return false;
|
|
}
|
|
return methodsIn(elements.getAllMembers(module)).stream()
|
|
.filter(anyBindingMethodValidator::isBindingMethod)
|
|
.map(ExecutableElement::getModifiers)
|
|
.anyMatch(modifiers -> !modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC));
|
|
}
|
|
|
|
private void validateNoScopeAnnotationsOnModuleElement(
|
|
TypeElement module, ModuleKind moduleKind, ValidationReport.Builder<TypeElement> report) {
|
|
for (AnnotationMirror scope : getAnnotatedAnnotations(module, Scope.class)) {
|
|
report.addError(
|
|
String.format(
|
|
"@%ss cannot be scoped. Did you mean to scope a method instead?",
|
|
moduleKind.annotation().getSimpleName()),
|
|
module,
|
|
scope);
|
|
}
|
|
}
|
|
|
|
private void validateSelfCycles(
|
|
TypeElement module, ValidationReport.Builder<TypeElement> builder) {
|
|
ModuleAnnotation moduleAnnotation = moduleAnnotation(module).get();
|
|
moduleAnnotation
|
|
.includesAsAnnotationValues()
|
|
.forEach(
|
|
value ->
|
|
value.accept(
|
|
new SimpleAnnotationValueVisitor8<Void, Void>() {
|
|
@Override
|
|
public Void visitType(TypeMirror includedModule, Void aVoid) {
|
|
if (MoreTypes.equivalence().equivalent(module.asType(), includedModule)) {
|
|
String moduleKind = moduleAnnotation.annotationName();
|
|
builder.addError(
|
|
String.format("@%s cannot include themselves.", moduleKind),
|
|
module,
|
|
moduleAnnotation.annotation(),
|
|
value);
|
|
}
|
|
return null;
|
|
}
|
|
},
|
|
null));
|
|
}
|
|
|
|
private void validateCompanionModule(
|
|
TypeElement module, ValidationReport.Builder<TypeElement> builder) {
|
|
checkArgument(metadataUtil.hasEnclosedCompanionObject(module));
|
|
TypeElement companionModule = metadataUtil.getEnclosedCompanionObject(module);
|
|
List<ExecutableElement> companionModuleMethods =
|
|
methodsIn(companionModule.getEnclosedElements());
|
|
List<ExecutableElement> companionBindingMethods = new ArrayList<>();
|
|
for (ExecutableElement companionModuleMethod : companionModuleMethods) {
|
|
if (anyBindingMethodValidator.isBindingMethod(companionModuleMethod)) {
|
|
builder.addSubreport(anyBindingMethodValidator.validate(companionModuleMethod));
|
|
companionBindingMethods.add(companionModuleMethod);
|
|
}
|
|
|
|
// On normal modules only overriding other binding methods is disallowed, but for companion
|
|
// objects we are prohibiting any override. For this can rely on checking the @Override
|
|
// annotation since the Kotlin compiler will always produce them for overriding methods.
|
|
if (isAnnotationPresent(companionModuleMethod, Override.class)) {
|
|
builder.addError(
|
|
"Binding method in companion object may not override another method.",
|
|
companionModuleMethod);
|
|
}
|
|
|
|
// TODO(danysantiago): Be strict about the usage of @JvmStatic, i.e. tell user to remove it.
|
|
}
|
|
|
|
ImmutableListMultimap<Name, ExecutableElement> bindingMethodsByName =
|
|
Multimaps.index(companionBindingMethods, ExecutableElement::getSimpleName);
|
|
validateMethodsWithSameName(builder, bindingMethodsByName);
|
|
|
|
// If there are provision methods, then check the visibility. Companion objects are composed by
|
|
// an inner class and a static field, it is not enough to check the visibility on the type
|
|
// element or the field, therefore we check the metadata.
|
|
if (!companionBindingMethods.isEmpty() && metadataUtil.isVisibilityPrivate(companionModule)) {
|
|
builder.addError(
|
|
"A Companion Module with binding methods cannot be private.", companionModule);
|
|
}
|
|
}
|
|
|
|
private void validateModuleBindings(
|
|
TypeElement module, ValidationReport.Builder<TypeElement> report) {
|
|
BindingGraph bindingGraph =
|
|
bindingGraphFactory.create(
|
|
componentDescriptorFactory.moduleComponentDescriptor(module), true)
|
|
.topLevelBindingGraph();
|
|
if (!bindingGraphValidator.isValid(bindingGraph)) {
|
|
// Since the validator uses a DiagnosticReporter to report errors, the ValdiationReport won't
|
|
// have any Items for them. We have to tell the ValidationReport that some errors were
|
|
// reported for the subject.
|
|
report.markDirty();
|
|
}
|
|
}
|
|
|
|
private static String formatListForErrorMessage(List<?> things) {
|
|
switch (things.size()) {
|
|
case 0:
|
|
return "";
|
|
case 1:
|
|
return things.get(0).toString();
|
|
default:
|
|
StringBuilder output = new StringBuilder();
|
|
Joiner.on(", ").appendTo(output, things.subList(0, things.size() - 1));
|
|
output.append(" and ").append(things.get(things.size() - 1));
|
|
return output.toString();
|
|
}
|
|
}
|
|
}
|