dagger.internal.codegen.ModuleValidator.java Source code

Java tutorial

Introduction

Here is the source code for dagger.internal.codegen.ModuleValidator.java

Source

/*
 * 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;

import static com.google.auto.common.MoreElements.isAnnotationPresent;
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 dagger.internal.codegen.ConfigurationAnnotations.getModuleIncludes;
import static dagger.internal.codegen.ConfigurationAnnotations.getModuleSubcomponents;
import static dagger.internal.codegen.ConfigurationAnnotations.getModules;
import static dagger.internal.codegen.ConfigurationAnnotations.getSubcomponentBuilder;
import static dagger.internal.codegen.DaggerElements.getAnnotationMirror;
import static dagger.internal.codegen.DaggerElements.isAnyAnnotationPresent;
import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_WITH_SAME_NAME;
import static dagger.internal.codegen.ErrorMessages.INCOMPATIBLE_MODULE_METHODS;
import static dagger.internal.codegen.ErrorMessages.METHOD_OVERRIDES_PROVIDES_METHOD;
import static dagger.internal.codegen.ErrorMessages.MODULES_WITH_TYPE_PARAMS_MUST_BE_ABSTRACT;
import static dagger.internal.codegen.ErrorMessages.ModuleMessages.moduleSubcomponentsDoesntHaveBuilder;
import static dagger.internal.codegen.ErrorMessages.ModuleMessages.moduleSubcomponentsIncludesBuilder;
import static dagger.internal.codegen.ErrorMessages.ModuleMessages.moduleSubcomponentsIncludesNonSubcomponent;
import static dagger.internal.codegen.ErrorMessages.PROVIDES_METHOD_OVERRIDES_ANOTHER;
import static dagger.internal.codegen.ErrorMessages.REFERENCED_MODULE_MUST_NOT_HAVE_TYPE_PARAMS;
import static dagger.internal.codegen.ErrorMessages.REFERENCED_MODULE_NOT_ANNOTATED;
import static dagger.internal.codegen.MoreAnnotationValues.asType;
import static dagger.internal.codegen.Util.toImmutableSet;
import static java.util.EnumSet.noneOf;
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.ArrayListMultimap;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
import dagger.Binds;
import dagger.Module;
import dagger.Subcomponent;
import dagger.multibindings.Multibinds;
import dagger.producers.ProducerModule;
import dagger.producers.ProductionSubcomponent;
import java.lang.annotation.Annotation;
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.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleTypeVisitor6;
import javax.lang.model.util.SimpleTypeVisitor8;
import javax.lang.model.util.Types;

/**
 * A {@linkplain ValidationReport validator} for {@link Module}s or {@link ProducerModule}s.
 *
 * @author Gregory Kick
 * @since 2.0
 */
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_BUILDER_TYPES = ImmutableSet
            .of(Subcomponent.Builder.class, ProductionSubcomponent.Builder.class);

    private final Types types;
    private final Elements elements;
    private final AnyBindingMethodValidator anyBindingMethodValidator;
    private final MethodSignatureFormatter methodSignatureFormatter;
    private final Map<TypeElement, ValidationReport<TypeElement>> cache = new HashMap<>();
    private final Set<TypeElement> knownModules = new HashSet<>();

    ModuleValidator(Types types, Elements elements, AnyBindingMethodValidator anyBindingMethodValidator,
            MethodSignatureFormatter methodSignatureFormatter) {
        this.types = types;
        this.elements = elements;
        this.anyBindingMethodValidator = anyBindingMethodValidator;
        this.methodSignatureFormatter = methodSignatureFormatter;
    }

    /**
     * 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) component}.
     */
    void addKnownModules(Collection<TypeElement> modules) {
        knownModules.addAll(modules);
    }

    /** Returns a validation report for a module type. */
    ValidationReport<TypeElement> validate(TypeElement module) {
        return cache.computeIfAbsent(module, this::validateUncached);
    }

    private ValidationReport<TypeElement> validateUncached(TypeElement module) {
        ValidationReport.Builder<TypeElement> builder = ValidationReport.about(module);
        ModuleDescriptor.Kind moduleKind = ModuleDescriptor.Kind.forAnnotatedElement(module).get();

        ListMultimap<String, ExecutableElement> allMethodsByName = ArrayListMultimap.create();
        ListMultimap<String, ExecutableElement> bindingMethodsByName = ArrayListMultimap.create();

        Set<ModuleMethodKind> methodKinds = noneOf(ModuleMethodKind.class);
        for (ExecutableElement moduleMethod : methodsIn(module.getEnclosedElements())) {
            if (anyBindingMethodValidator.isBindingMethod(moduleMethod)) {
                builder.addSubreport(anyBindingMethodValidator.validate(moduleMethod));
            }
            if (isAnyAnnotationPresent(moduleMethod,
                    ImmutableSet.of(moduleKind.methodAnnotation(), Binds.class, Multibinds.class))) {
                bindingMethodsByName.put(moduleMethod.getSimpleName().toString(), moduleMethod);
                methodKinds.add(ModuleMethodKind.ofMethod(moduleMethod));
            }
            allMethodsByName.put(moduleMethod.getSimpleName().toString(), moduleMethod);
        }

        if (methodKinds.containsAll(
                EnumSet.of(ModuleMethodKind.ABSTRACT_DECLARATION, ModuleMethodKind.INSTANCE_BINDING))) {
            builder.addError(String.format(INCOMPATIBLE_MODULE_METHODS,
                    moduleKind.moduleAnnotation().getSimpleName(), moduleKind.methodAnnotation().getSimpleName()));
        }

        validateModuleVisibility(module, moduleKind, builder);
        validateMethodsWithSameName(moduleKind, builder, bindingMethodsByName);
        if (module.getKind() != ElementKind.INTERFACE) {
            validateProvidesOverrides(module, moduleKind, builder, allMethodsByName, bindingMethodsByName);
        }
        validateModifiers(module, builder);
        validateReferencedModules(module, moduleKind, builder);
        validateReferencedSubcomponents(module, moduleKind, builder);

        return builder.build();
    }

    private void validateReferencedSubcomponents(final TypeElement subject, ModuleDescriptor.Kind moduleKind,
            final ValidationReport.Builder<TypeElement> builder) {
        final AnnotationMirror moduleAnnotation = moduleKind.getModuleAnnotationMirror(subject).get();
        // TODO(ronshapiro): use validateTypesAreDeclared when it is checked in
        for (TypeMirror subcomponentAttribute : getModuleSubcomponents(moduleAnnotation)) {
            subcomponentAttribute.accept(new SimpleTypeVisitor6<Void, Void>() {
                @Override
                protected Void defaultAction(TypeMirror e, Void aVoid) {
                    builder.addError(e + " is not a valid subcomponent type", subject, moduleAnnotation);
                    return null;
                }

                @Override
                public Void visitDeclared(DeclaredType declaredType, Void aVoid) {
                    TypeElement attributeType = MoreTypes.asTypeElement(declaredType);
                    if (isAnyAnnotationPresent(attributeType, SUBCOMPONENT_TYPES)) {
                        validateSubcomponentHasBuilder(attributeType, moduleAnnotation, builder);
                    } else {
                        builder.addError(
                                isAnyAnnotationPresent(attributeType, SUBCOMPONENT_BUILDER_TYPES)
                                        ? moduleSubcomponentsIncludesBuilder(attributeType)
                                        : moduleSubcomponentsIncludesNonSubcomponent(attributeType),
                                subject, moduleAnnotation);
                    }

                    return null;
                }
            }, null);
        }
    }

    private static void validateSubcomponentHasBuilder(TypeElement subcomponentAttribute,
            AnnotationMirror moduleAnnotation, ValidationReport.Builder<TypeElement> builder) {
        if (getSubcomponentBuilder(subcomponentAttribute).isPresent()) {
            return;
        }
        builder.addError(moduleSubcomponentsDoesntHaveBuilder(subcomponentAttribute, moduleAnnotation),
                builder.getSubject(), 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_PARAMS_MUST_BE_ABSTRACT, subject);
        }
    }

    private void validateMethodsWithSameName(ModuleDescriptor.Kind moduleKind,
            ValidationReport.Builder<TypeElement> builder,
            ListMultimap<String, ExecutableElement> bindingMethodsByName) {
        for (Entry<String, Collection<ExecutableElement>> entry : bindingMethodsByName.asMap().entrySet()) {
            if (entry.getValue().size() > 1) {
                for (ExecutableElement offendingMethod : entry.getValue()) {
                    builder.addError(String.format(BINDING_METHOD_WITH_SAME_NAME,
                            moduleKind.methodAnnotation().getSimpleName()), offendingMethod);
                }
            }
        }
    }

    private void validateReferencedModules(TypeElement subject, ModuleDescriptor.Kind moduleKind,
            ValidationReport.Builder<TypeElement> builder) {
        // Validate that all the modules we include are valid for inclusion.
        AnnotationMirror mirror = moduleKind.getModuleAnnotationMirror(subject).get();
        builder.addSubreport(validateReferencedModules(subject, mirror, moduleKind.includesKinds()));
    }

    /**
     * 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<ModuleDescriptor.Kind> validModuleKinds) {
        ValidationReport.Builder<TypeElement> subreport = ValidationReport.about(annotatedType);
        ImmutableSet<? extends Class<? extends Annotation>> validModuleAnnotations = validModuleKinds.stream()
                .map(ModuleDescriptor.Kind::moduleAnnotation).collect(toImmutableSet());

        for (AnnotationValue includedModule : getModules(annotatedType, 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(REFERENCED_MODULE_MUST_NOT_HAVE_TYPE_PARAMS, module.getQualifiedName());
                    }
                    if (!isAnyAnnotationPresent(module, validModuleAnnotations)) {
                        reportError(REFERENCED_MODULE_NOT_ANNOTATED, module.getQualifiedName(),
                                (validModuleAnnotations.size() > 1 ? "one of " : "") + validModuleAnnotations
                                        .stream().map(otherClass -> "@" + otherClass.getSimpleName())
                                        .collect(joining(", ")));
                    } else if (knownModules.contains(module) && !validate(module).isClean()) {
                        reportError("%s has errors", module.getQualifiedName());
                    }
                    return null;
                }

                private void reportError(String format, Object... args) {
                    subreport.addError(String.format(format, args), annotatedType, annotation, includedModule);
                }
            }, null);
        }
        return subreport.build();
    }

    private void validateProvidesOverrides(TypeElement subject, ModuleDescriptor.Kind moduleKind,
            ValidationReport.Builder<TypeElement> builder, ListMultimap<String, ExecutableElement> allMethodsByName,
            ListMultimap<String, ExecutableElement> bindingMethodsByName) {
        // For every @Provides 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 method marked @Provides in Parent, and "c" because Child is defining an @Provides
        // method that overrides Parent.
        TypeElement currentClass = subject;
        TypeMirror objectType = elements.getTypeElement(Object.class.getCanonicalName()).asType();
        // We keep track of methods that failed so we don't spam with multiple failures.
        Set<ExecutableElement> failedMethods = Sets.newHashSet();
        while (!types.isSameType(currentClass.getSuperclass(), objectType)) {
            currentClass = MoreElements.asType(types.asElement(currentClass.getSuperclass()));
            List<ExecutableElement> superclassMethods = ElementFilter.methodsIn(currentClass.getEnclosedElements());
            for (ExecutableElement superclassMethod : superclassMethods) {
                String name = superclassMethod.getSimpleName().toString();
                // For each method in the superclass, confirm our @Provides methods don't override it
                for (ExecutableElement providesMethod : bindingMethodsByName.get(name)) {
                    if (!failedMethods.contains(providesMethod)
                            && elements.overrides(providesMethod, superclassMethod, subject)) {
                        failedMethods.add(providesMethod);
                        /**
                        builder.addError(
                            String.format(
                                PROVIDES_METHOD_OVERRIDES_ANOTHER,
                                moduleKind.methodAnnotation().getSimpleName(),
                                methodSignatureFormatter.format(superclassMethod)),
                            providesMethod);
                         **/
                    }
                }
                // For each @Provides method in superclass, confirm our methods don't override it.
                if (isAnnotationPresent(superclassMethod, moduleKind.methodAnnotation())) {
                    for (ExecutableElement method : allMethodsByName.get(name)) {
                        if (!failedMethods.contains(method)
                                && elements.overrides(method, superclassMethod, subject)) {
                            failedMethods.add(method);
                            builder.addError(String.format(METHOD_OVERRIDES_PROVIDES_METHOD,
                                    moduleKind.methodAnnotation().getSimpleName(),
                                    methodSignatureFormatter.format(superclassMethod)), method);
                        }
                    }
                }
                allMethodsByName.put(superclassMethod.getSimpleName().toString(), superclassMethod);
            }
        }
    }

    private void validateModuleVisibility(final TypeElement moduleElement, ModuleDescriptor.Kind moduleKind,
            final ValidationReport.Builder<?> reportBuilder) {
        Visibility moduleVisibility = Visibility.ofElement(moduleElement);
        if (moduleVisibility.equals(PRIVATE)) {
            reportBuilder.addError("Modules cannot be private.", moduleElement);
        } else if (effectiveVisibilityOfElement(moduleElement).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 (moduleVisibility.equals(PUBLIC)) {
                ImmutableSet<Element> nonPublicModules = FluentIterable
                        .from(getModuleIncludes(
                                getAnnotationMirror(moduleElement, moduleKind.moduleAnnotation()).get()))
                        .transform(types::asElement)
                        .filter(element -> effectiveVisibilityOfElement(element).compareTo(PUBLIC) < 0).toSet();
                if (!nonPublicModules.isEmpty()) {
                    reportBuilder.addError(String.format(
                            "This module is public, but it includes non-public "
                                    + "(or effectively non-public) modules. "
                                    + "Either reduce the visibility of this module or make %s public.",
                            formatListForErrorMessage(nonPublicModules.asList())), moduleElement);
                }
            }
            break;
        default:
            throw new AssertionError();
        }
    }

    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();
        }
    }
}