All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dagger.internal.codegen.validation.DiagnosticMessageGenerator Maven / Gradle / Ivy

There is a newer version: 2.52
Show newest version
/*
 * 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.XElementKt.isTypeElement;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Verify.verify;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.getLast;
import static com.google.common.collect.Iterables.indexOf;
import static com.google.common.collect.Iterables.transform;
import static dagger.internal.codegen.base.ElementFormatter.elementToString;
import static dagger.internal.codegen.extension.DaggerGraphs.shortestPath;
import static dagger.internal.codegen.extension.DaggerStreams.instancesOf;
import static dagger.internal.codegen.extension.DaggerStreams.presentValues;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.xprocessing.XElements.asExecutable;
import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;
import static dagger.internal.codegen.xprocessing.XElements.isExecutable;
import static java.util.Collections.min;
import static java.util.Comparator.comparing;
import static java.util.Comparator.comparingInt;

import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Table;
import dagger.internal.codegen.base.ElementFormatter;
import dagger.internal.codegen.base.Formatter;
import dagger.internal.codegen.binding.DependencyRequestFormatter;
import dagger.internal.codegen.model.Binding;
import dagger.internal.codegen.model.BindingGraph;
import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
import dagger.internal.codegen.model.BindingGraph.Edge;
import dagger.internal.codegen.model.BindingGraph.MaybeBinding;
import dagger.internal.codegen.model.BindingGraph.Node;
import dagger.internal.codegen.model.ComponentPath;
import dagger.internal.codegen.model.DaggerElement;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import javax.inject.Inject;

/** Helper class for generating diagnostic messages. */
public final class DiagnosticMessageGenerator {

  /** Injectable factory for {@code DiagnosticMessageGenerator}. */
  public static final class Factory {
    private final DependencyRequestFormatter dependencyRequestFormatter;
    private final ElementFormatter elementFormatter;

    @Inject
    Factory(
        DependencyRequestFormatter dependencyRequestFormatter,
        ElementFormatter elementFormatter) {
      this.dependencyRequestFormatter = dependencyRequestFormatter;
      this.elementFormatter = elementFormatter;
    }

    /** Creates a {@code DiagnosticMessageGenerator} for the given binding graph. */
    public DiagnosticMessageGenerator create(BindingGraph graph) {
      return new DiagnosticMessageGenerator(graph, dependencyRequestFormatter, elementFormatter);
    }
  }

  private final BindingGraph graph;
  private final DependencyRequestFormatter dependencyRequestFormatter;
  private final ElementFormatter elementFormatter;

  /** A cached function from type to all of its supertypes in breadth-first order. */
  private final Function> supertypes;

  /** The shortest path (value) from an entry point (column) to a binding (row). */
  private final Table> shortestPaths =
      HashBasedTable.create();

  private static  Function memoize(Function uncached) {
    // If Android Guava is on the processor path, then c.g.c.b.Function (which LoadingCache
    // implements) does not extend j.u.f.Function.
    // TODO(erichang): Fix current breakages and try to remove this to enforce not having this on
    // processor path.

    // First, explicitly convert uncached to c.g.c.b.Function because CacheLoader.from() expects
    // one.
    com.google.common.base.Function uncachedAsBaseFunction = uncached::apply;

    LoadingCache cache =
        CacheBuilder.newBuilder().build(CacheLoader.from(uncachedAsBaseFunction));

    // Second, explicitly convert LoadingCache to j.u.f.Function.
    @SuppressWarnings("deprecation") // uncachedAsBaseFunction throws only unchecked exceptions
    Function memoized = cache::apply;

    return memoized;
  }

  private DiagnosticMessageGenerator(
      BindingGraph graph,
      DependencyRequestFormatter dependencyRequestFormatter,
      ElementFormatter elementFormatter) {
    this.graph = graph;
    this.dependencyRequestFormatter = dependencyRequestFormatter;
    this.elementFormatter = elementFormatter;
    supertypes =
        memoize(component -> transform(component.getType().getSuperTypes(), XType::getTypeElement));
  }

  public String getMessage(MaybeBinding binding) {
    ImmutableSet entryPoints = graph.entryPointEdgesDependingOnBinding(binding);
    ImmutableSet requests = requests(binding);
    ImmutableList dependencyTrace = dependencyTrace(binding, entryPoints);

    return getMessageInternal(dependencyTrace, requests, entryPoints);
  }

  public String getMessage(DependencyEdge dependencyEdge) {
    ImmutableSet requests = ImmutableSet.of(dependencyEdge);

    ImmutableSet entryPoints;
    ImmutableList dependencyTrace;
    if (dependencyEdge.isEntryPoint()) {
      entryPoints = ImmutableSet.of(dependencyEdge);
      dependencyTrace = ImmutableList.of(dependencyEdge);
    } else {
      // It's not an entry point, so it's part of a binding
      Binding binding = (Binding) source(dependencyEdge);
      entryPoints = graph.entryPointEdgesDependingOnBinding(binding);
      dependencyTrace =
          ImmutableList.builder()
              .add(dependencyEdge)
              .addAll(dependencyTrace(binding, entryPoints))
              .build();
    }

    return getMessageInternal(dependencyTrace, requests, entryPoints);
  }

  private String getMessageInternal(
      ImmutableList dependencyTrace,
      ImmutableSet requests,
      ImmutableSet entryPoints) {
    StringBuilder message =
        graph.isFullBindingGraph()
            ? new StringBuilder()
            : new StringBuilder(dependencyTrace.size() * 100 /* a guess heuristic */);

    // Print the dependency trace unless it's a full binding graph
    if (!graph.isFullBindingGraph()) {
      dependencyTrace.forEach(
          edge -> dependencyRequestFormatter.appendFormatLine(message, edge.dependencyRequest()));
      if (!dependencyTrace.isEmpty()) {
        appendComponentPathUnlessAtRoot(message, source(getLast(dependencyTrace)));
      }
    }
    message.append(getRequestsNotInTrace(dependencyTrace, requests, entryPoints));
    return message.toString();
  }

  public String getRequestsNotInTrace(
      ImmutableList dependencyTrace,
      ImmutableSet requests,
      ImmutableSet entryPoints) {
    StringBuilder message = new StringBuilder();
    // Print any dependency requests that aren't shown as part of the dependency trace.
    ImmutableSet requestsToPrint =
        requests.stream()
            // if printing entry points, skip entry points and the traced request
            .filter(
                request ->
                    graph.isFullBindingGraph()
                        || (!request.isEntryPoint() && !isTracedRequest(dependencyTrace, request)))
            .map(request -> request.dependencyRequest().requestElement())
            .flatMap(presentValues())
            .map(DaggerElement::xprocessing)
            .collect(toImmutableSet());
    if (!requestsToPrint.isEmpty()) {
      message
          .append("\nIt is")
          .append(graph.isFullBindingGraph() ? " " : " also ")
          .append("requested at:");
      elementFormatter.formatIndentedList(message, requestsToPrint, 1);
    }

    // Print the remaining entry points, showing which component they're in, unless it's a full
    // binding graph
    if (!graph.isFullBindingGraph() && entryPoints.size() > 1) {
      message.append("\nThe following other entry points also depend on it:");
      entryPointFormatter.formatIndentedList(
          message,
          entryPoints.stream()
              .filter(entryPoint -> !entryPoint.equals(getLast(dependencyTrace)))
              .sorted(
                  // 1. List entry points in components closest to the root first.
                  // 2. List entry points declared in a component before those in a supertype.
                  // 3. List entry points in declaration order in their declaring type.
                  rootComponentFirst()
                      .thenComparing(nearestComponentSupertypeFirst())
                      .thenComparing(requestElementDeclarationOrder()))
              .collect(toImmutableList()),
          1);
    }
    return message.toString();
  }

  public void appendComponentPathUnlessAtRoot(StringBuilder message, Node node) {
    if (!node.componentPath().equals(graph.rootComponentNode().componentPath())) {
      message.append(String.format(" [%s]", node.componentPath()));
    }
  }

  private final Formatter entryPointFormatter =
      new Formatter() {
        @Override
        public String format(DependencyEdge object) {
          XElement requestElement = object.dependencyRequest().requestElement().get().xprocessing();
          StringBuilder builder = new StringBuilder(elementToString(requestElement));

          // For entry points declared in subcomponents or supertypes of the root component,
          // append the component path to make clear to the user which component it's in.
          ComponentPath componentPath = source(object).componentPath();
          if (!componentPath.atRoot()
              || !requestElement
                  .getEnclosingElement()
                  .equals(componentPath.rootComponent().xprocessing())) {
            builder.append(String.format(" [%s]", componentPath));
          }
          return builder.toString();
        }
      };

  private static boolean isTracedRequest(
      ImmutableList dependencyTrace, DependencyEdge request) {
    return !dependencyTrace.isEmpty() && request.equals(dependencyTrace.get(0));
  }

  /**
   * Returns the dependency trace from one of the {@code entryPoints} to {@code binding} to {@code
   * message} as a list ending with the entry point.
   */
  // TODO(ronshapiro): Adding a DependencyPath type to dagger.internal.codegen.model could be
  // useful, i.e.
  // bindingGraph.shortestPathFromEntryPoint(DependencyEdge, MaybeBindingNode)
  public ImmutableList dependencyTrace(
      MaybeBinding binding, ImmutableSet entryPoints) {
    // Module binding graphs may have bindings unreachable from any entry points. If there are
    // no entry points for this DiagnosticInfo, don't try to print a dependency trace.
    if (entryPoints.isEmpty()) {
      return ImmutableList.of();
    }
    // Show the full dependency trace for one entry point.
    DependencyEdge entryPointForTrace =
        min(
            entryPoints,
            // prefer entry points in components closest to the root
            rootComponentFirst()
                // then prefer entry points with a short dependency path to the error
                .thenComparing(shortestDependencyPathFirst(binding))
                // then prefer entry points declared in the component to those declared in a
                // supertype
                .thenComparing(nearestComponentSupertypeFirst())
                // finally prefer entry points declared first in their enclosing type
                .thenComparing(requestElementDeclarationOrder()));

    ImmutableList shortestBindingPath =
        shortestPathFromEntryPoint(entryPointForTrace, binding);
    verify(
        !shortestBindingPath.isEmpty(),
        "no dependency path from %s to %s in %s",
        entryPointForTrace,
        binding,
        graph);

    ImmutableList.Builder dependencyTrace = ImmutableList.builder();
    dependencyTrace.add(entryPointForTrace);
    for (int i = 0; i < shortestBindingPath.size() - 1; i++) {
      Set dependenciesBetween =
          graph
              .network()
              .edgesConnecting(shortestBindingPath.get(i), shortestBindingPath.get(i + 1));
      // If a binding requests a key more than once, any of them should be fine to get to the
      // shortest path
      dependencyTrace.add((DependencyEdge) Iterables.get(dependenciesBetween, 0));
    }
    return dependencyTrace.build().reverse();
  }

  /** Returns all the nonsynthetic dependency requests for a binding. */
  public ImmutableSet requests(MaybeBinding binding) {
    return graph.network().inEdges(binding).stream()
        .flatMap(instancesOf(DependencyEdge.class))
        .filter(edge -> edge.dependencyRequest().requestElement().isPresent())
        .sorted(requestEnclosingTypeName().thenComparing(requestElementDeclarationOrder()))
        .collect(toImmutableSet());
  }

  /**
   * Returns a comparator that sorts entry points in components whose paths from the root are
   * shorter first.
   */
  private Comparator rootComponentFirst() {
    return comparingInt(entryPoint -> source(entryPoint).componentPath().components().size());
  }

  /**
   * Returns a comparator that puts entry points whose shortest dependency path to {@code binding}
   * is shortest first.
   */
  private Comparator shortestDependencyPathFirst(MaybeBinding binding) {
    return comparing(entryPoint -> shortestPathFromEntryPoint(entryPoint, binding).size());
  }

  private ImmutableList shortestPathFromEntryPoint(
      DependencyEdge entryPoint, MaybeBinding binding) {
    return shortestPaths
        .row(binding)
        .computeIfAbsent(
            entryPoint,
            ep ->
                shortestPath(
                    node ->
                        filter(graph.network().successors(node), MaybeBinding.class::isInstance),
                    graph.network().incidentNodes(ep).target(),
                    binding));
  }

  /**
   * Returns a comparator that sorts entry points in by the distance of the type that declares them
   * from the type of the component that contains them.
   *
   * 

For instance, an entry point declared directly in the component type would sort before one * declared in a direct supertype, which would sort before one declared in a supertype of a * supertype. */ private Comparator nearestComponentSupertypeFirst() { return comparingInt( entryPoint -> indexOf( supertypes.apply(componentContainingEntryPoint(entryPoint)), equalTo(typeDeclaringEntryPoint(entryPoint)))); } private XTypeElement componentContainingEntryPoint(DependencyEdge entryPoint) { return source(entryPoint).componentPath().currentComponent().xprocessing(); } private XTypeElement typeDeclaringEntryPoint(DependencyEdge entryPoint) { return asTypeElement( entryPoint.dependencyRequest().requestElement().get().xprocessing().getEnclosingElement()); } /** * Returns a comparator that sorts dependency edges lexicographically by the qualified name of the * type that contains them. Only appropriate for edges with request elements. */ private Comparator requestEnclosingTypeName() { return comparing( edge -> closestEnclosingTypeElement( edge.dependencyRequest().requestElement().get().xprocessing()) .getQualifiedName()); } /** * Returns a comparator that sorts edges in the order in which their request elements were * declared in their declaring type. * *

Only useful to compare edges whose request elements were declared in the same type. */ private Comparator requestElementDeclarationOrder() { return comparing( edge -> edge.dependencyRequest().requestElement().get().xprocessing(), // TODO(bcorso): This is inefficient as it requires each element to iterate through all of // its siblings to find its order. Ideally, the order of all elements would be calculated in // a single pass and cached, but the organization of the current code makes that a bit // difficult. I'm leaving this for now since this is only called on failures. comparing( element -> { XElement enclosingElement = element.getEnclosingElement(); checkState(isTypeElement(enclosingElement) || isExecutable(enclosingElement)); List siblings = isTypeElement(enclosingElement) ? asTypeElement(enclosingElement).getEnclosedElements() // For parameter elements, element.getEnclosingElement().getEnclosedElements() // is empty, so instead look at the parameter list of the enclosing executable : asExecutable(enclosingElement).getParameters(); return siblings.indexOf(element); })); } private Node source(Edge edge) { return graph.network().incidentNodes(edge).source(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy