dagger.internal.codegen.DuplicateBindingsValidation.java Source code

Java tutorial

Introduction

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

Source

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

import static com.google.common.base.Verify.verify;
import static com.google.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.DaggerStreams.instancesOf;
import static dagger.internal.codegen.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.DaggerStreams.toImmutableSetMultimap;
import static dagger.internal.codegen.DuplicateBindingsValidation.SourceAndRequest.indexEdgesBySourceAndRequest;
import static dagger.internal.codegen.Formatter.INDENT;
import static dagger.internal.codegen.Optionals.emptiesLast;
import static java.util.Comparator.comparing;
import static javax.tools.Diagnostic.Kind.ERROR;

import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import dagger.model.BindingGraph;
import dagger.model.BindingGraph.BindingNode;
import dagger.model.BindingGraph.DependencyEdge;
import dagger.model.BindingGraph.Node;
import dagger.model.DependencyRequest;
import dagger.spi.BindingGraphPlugin;
import dagger.spi.DiagnosticReporter;
import java.util.Comparator;
import java.util.Set;
import javax.inject.Inject;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

/** Reports errors for conflicting bindings with the same key. */
final class DuplicateBindingsValidation implements BindingGraphPlugin {

    // 1. contributing module or enclosing type
    // 2. binding element's simple name
    // 3. binding element's type
    private static final Comparator<BindingDeclaration> BINDING_DECLARATION_COMPARATOR = comparing(
            (BindingDeclaration declaration) -> declaration.contributingModule().isPresent()
                    ? declaration.contributingModule()
                    : declaration.bindingTypeElement(),
            emptiesLast(comparing((TypeElement type) -> type.getQualifiedName().toString()))).thenComparing(
                    (BindingDeclaration declaration) -> declaration.bindingElement(),
                    emptiesLast(comparing((Element element) -> element.getSimpleName().toString())
                            .thenComparing((Element element) -> element.asType().toString())));

    private final BindingDeclarationFormatter bindingDeclarationFormatter;

    @Inject
    DuplicateBindingsValidation(BindingDeclarationFormatter bindingDeclarationFormatter) {
        this.bindingDeclarationFormatter = bindingDeclarationFormatter;
    }

    @Override
    public String pluginName() {
        return "Dagger/DuplicateBindings";
    }

    @Override
    public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {
        Multimaps.asMap(indexEdgesBySourceAndRequest(bindingGraph)).forEach((sourceAndRequest, dependencyEdges) -> {
            if (dependencyEdges.size() > 1) {
                reportDuplicateBindings(sourceAndRequest.request(), dependencyEdges, bindingGraph,
                        diagnosticReporter);
            }
        });
    }

    private void reportDuplicateBindings(DependencyRequest dependencyRequest,
            Set<DependencyEdge> duplicateDependencies, BindingGraph bindingGraph,
            DiagnosticReporter diagnosticReporter) {
        ImmutableSet<BindingNode> duplicateBindings = duplicateDependencies.stream()
                .map(edge -> bindingGraph.incidentNodes(edge).target()).flatMap(instancesOf(BindingNode.class))
                .collect(toImmutableSet());
        diagnosticReporter.reportDependency(ERROR, Iterables.get(duplicateDependencies, 0),
                Iterables.any(duplicateBindings, node -> node.binding().kind().isMultibinding())
                        ? incompatibleBindingsMessage(dependencyRequest, duplicateBindings, bindingGraph)
                        : duplicateBindingMessage(dependencyRequest, duplicateBindings, bindingGraph));
    }

    private String duplicateBindingMessage(DependencyRequest dependencyRequest,
            ImmutableSet<BindingNode> duplicateBindings, BindingGraph graph) {
        StringBuilder message = new StringBuilder().append(dependencyRequest.key())
                .append(" is bound multiple times:");
        formatDeclarations(message, 1, declarations(graph, duplicateBindings));
        return message.toString();
    }

    private String incompatibleBindingsMessage(DependencyRequest dependencyRequest,
            ImmutableSet<BindingNode> duplicateBindings, BindingGraph graph) {
        ImmutableSet<BindingNode> multibindings = duplicateBindings.stream()
                .filter(node -> node.binding().kind().isMultibinding()).collect(toImmutableSet());
        verify(multibindings.size() == 1, "expected only one multibinding for %s: %s", dependencyRequest,
                multibindings);
        StringBuilder message = new StringBuilder();
        java.util.Formatter messageFormatter = new java.util.Formatter(message);
        messageFormatter.format("%s has incompatible bindings or declarations:\n", dependencyRequest.key());
        message.append(INDENT);
        BindingNode multibinding = getOnlyElement(multibindings);
        messageFormatter.format("%s bindings and declarations:", multibindingTypeString(multibinding));
        formatDeclarations(message, 2, declarations(graph, multibindings));

        Set<BindingNode> uniqueBindings = Sets.filter(duplicateBindings, binding -> !binding.equals(multibinding));
        message.append(INDENT).append("Unique bindings and declarations:");
        formatDeclarations(message, 2, Sets.filter(declarations(graph, uniqueBindings),
                declaration -> !(declaration instanceof MultibindingDeclaration)));
        return message.toString();
    }

    private void formatDeclarations(StringBuilder builder, int indentLevel,
            Iterable<? extends BindingDeclaration> bindingDeclarations) {
        bindingDeclarationFormatter.formatIndentedList(builder, ImmutableList.copyOf(bindingDeclarations),
                indentLevel);
        builder.append('\n');
    }

    private ImmutableSet<BindingDeclaration> declarations(BindingGraph graph, Set<BindingNode> bindings) {
        return bindings.stream().flatMap(node -> declarations(graph, node).stream()).distinct()
                .sorted(BINDING_DECLARATION_COMPARATOR).collect(toImmutableSet());
    }

    private ImmutableSet<BindingDeclaration> declarations(BindingGraph graph, BindingNode node) {
        ImmutableSet.Builder<BindingDeclaration> declarations = ImmutableSet.builder();
        ((BindingNodeImpl) node).associatedDeclarations().forEach(declarations::add);
        if (node.binding() instanceof BindingDeclaration) {
            BindingDeclaration declaration = ((BindingDeclaration) node.binding());
            if (bindingDeclarationFormatter.canFormat(declaration)) {
                declarations.add(declaration);
            } else {
                graph.successors(node).stream().flatMap(instancesOf(BindingNode.class))
                        .flatMap(dependency -> declarations(graph, dependency).stream()).forEach(declarations::add);
            }
        }
        return declarations.build();
    }

    private String multibindingTypeString(BindingNode multibinding) {
        switch (multibinding.binding().kind()) {
        case MULTIBOUND_MAP:
            return "Map";
        case MULTIBOUND_SET:
            return "Set";
        default:
            throw new AssertionError(multibinding);
        }
    }

    @AutoValue
    abstract static class SourceAndRequest {

        abstract Node source();

        abstract DependencyRequest request();

        static ImmutableSetMultimap<SourceAndRequest, DependencyEdge> indexEdgesBySourceAndRequest(
                BindingGraph bindingGraph) {
            return bindingGraph.dependencyEdges().stream()
                    .collect(toImmutableSetMultimap(
                            edge -> create(bindingGraph.incidentNodes(edge).source(), edge.dependencyRequest()),
                            edge -> edge));
        }

        static SourceAndRequest create(Node source, DependencyRequest request) {
            return new AutoValue_DuplicateBindingsValidation_SourceAndRequest(source, request);
        }
    }
}