314 lines
10 KiB
Java
314 lines
10 KiB
Java
/*
|
|
* Copyright (C) 2017 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.example.spi;
|
|
|
|
import static java.util.UUID.randomUUID;
|
|
import static java.util.regex.Matcher.quoteReplacement;
|
|
import static java.util.stream.Collectors.groupingBy;
|
|
|
|
import com.google.auto.service.AutoService;
|
|
import com.google.common.base.Joiner;
|
|
import com.google.common.base.Strings;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.Iterators;
|
|
import com.google.common.graph.EndpointPair;
|
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
|
import com.squareup.javapoet.ClassName;
|
|
import dagger.model.Binding;
|
|
import dagger.model.BindingGraph;
|
|
import dagger.model.BindingGraph.ChildFactoryMethodEdge;
|
|
import dagger.model.BindingGraph.DependencyEdge;
|
|
import dagger.model.BindingGraph.Edge;
|
|
import dagger.model.BindingGraph.MaybeBinding;
|
|
import dagger.model.BindingGraph.MissingBinding;
|
|
import dagger.model.BindingGraph.Node;
|
|
import dagger.model.BindingGraph.SubcomponentCreatorBindingEdge;
|
|
import dagger.model.BindingKind;
|
|
import dagger.model.ComponentPath;
|
|
import dagger.spi.BindingGraphPlugin;
|
|
import dagger.spi.DiagnosticReporter;
|
|
import java.io.IOException;
|
|
import java.io.PrintWriter;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.UUID;
|
|
import java.util.stream.Collectors;
|
|
import javax.annotation.processing.Filer;
|
|
import javax.lang.model.element.TypeElement;
|
|
import javax.tools.FileObject;
|
|
import javax.tools.StandardLocation;
|
|
|
|
/**
|
|
* Experimental visualizer used as a proof-of-concept for {@link BindingGraphPlugin}.
|
|
*
|
|
* <p>For each component, writes a <a href=http://www.graphviz.org/content/dot-language>DOT file</a>
|
|
* in the same package. The file name is the name of the component type (with enclosing type names,
|
|
* joined by underscores, preceding it), with a {@code .dot} extension.
|
|
*
|
|
* <p>For example, for a nested component type {@code Foo.Bar} this will generate a file {@code
|
|
* Foo_Bar.dot}.
|
|
*/
|
|
@AutoService(BindingGraphPlugin.class)
|
|
public final class BindingGraphVisualizer implements BindingGraphPlugin {
|
|
private Filer filer;
|
|
|
|
@Override
|
|
public void initFiler(Filer filer) {
|
|
this.filer = filer;
|
|
}
|
|
|
|
/** Graphviz color names to use for binding nodes within each component. */
|
|
private static final ImmutableList<String> COMPONENT_COLORS =
|
|
ImmutableList.of(
|
|
"/set312/1",
|
|
"/set312/2",
|
|
"/set312/3",
|
|
"/set312/4",
|
|
"/set312/5",
|
|
"/set312/6",
|
|
"/set312/7",
|
|
"/set312/8",
|
|
"/set312/9",
|
|
"/set312/10",
|
|
"/set312/11",
|
|
"/set312/12");
|
|
|
|
@Override
|
|
public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {
|
|
TypeElement componentElement =
|
|
bindingGraph.rootComponentNode().componentPath().currentComponent();
|
|
DotGraph graph = new NodesGraph(bindingGraph).graph();
|
|
ClassName componentName = ClassName.get(componentElement);
|
|
try {
|
|
FileObject file =
|
|
filer
|
|
.createResource(
|
|
StandardLocation.CLASS_OUTPUT,
|
|
componentName.packageName(),
|
|
Joiner.on('_').join(componentName.simpleNames()) + ".dot",
|
|
componentElement);
|
|
try (PrintWriter writer = new PrintWriter(file.openWriter())) {
|
|
graph.write(0, writer);
|
|
}
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
private abstract static class Indented {
|
|
|
|
abstract void write(int level, PrintWriter writer);
|
|
|
|
@CanIgnoreReturnValue
|
|
PrintWriter indent(int level, PrintWriter writer) {
|
|
writer.print(Strings.repeat(" ", level * 2));
|
|
return writer;
|
|
}
|
|
}
|
|
|
|
static class DotGraph extends Indented {
|
|
private final String header;
|
|
private final List<Indented> elements = new ArrayList<>();
|
|
|
|
DotGraph(String header) {
|
|
this.header = header;
|
|
}
|
|
|
|
@CanIgnoreReturnValue
|
|
DotGraph add(Indented element) {
|
|
elements.add(element);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
void write(int level, PrintWriter writer) {
|
|
indent(level, writer);
|
|
writer.println(header + " {");
|
|
for (Indented element : elements) {
|
|
element.write(level + 1, writer);
|
|
}
|
|
indent(level, writer);
|
|
writer.println("}");
|
|
}
|
|
}
|
|
|
|
static class DotStatement<S extends DotStatement<S>> extends Indented {
|
|
private final String base;
|
|
private final Map<String, Object> attributes = new LinkedHashMap<>();
|
|
|
|
DotStatement(String base) {
|
|
this.base = base;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@CanIgnoreReturnValue
|
|
S addAttribute(String name, Object value) {
|
|
attributes.put(name, value);
|
|
return (S) this;
|
|
}
|
|
|
|
@CanIgnoreReturnValue
|
|
S addAttributeFormat(String name, String format, Object... args) {
|
|
return addAttribute(name, String.format(format, args));
|
|
}
|
|
|
|
@Override
|
|
void write(int level, PrintWriter writer) {
|
|
indent(level, writer);
|
|
writer.print(base);
|
|
if (!attributes.isEmpty()) {
|
|
writer.print(
|
|
attributes
|
|
.entrySet()
|
|
.stream()
|
|
.map(
|
|
entry ->
|
|
String.format("%s=%s", entry.getKey(), quote(entry.getValue().toString())))
|
|
.collect(Collectors.joining(", ", " [", "]")));
|
|
}
|
|
writer.println();
|
|
}
|
|
}
|
|
|
|
private static String quote(String string) {
|
|
return '"' + string.replaceAll("\"", quoteReplacement("\\\"")) + '"';
|
|
}
|
|
|
|
static class DotNode extends DotStatement<DotNode> {
|
|
DotNode(Object nodeName) {
|
|
super(quote(nodeName.toString()));
|
|
}
|
|
}
|
|
|
|
static class DotEdge extends DotStatement<DotEdge> {
|
|
DotEdge(Object leftNode, Object rightNode) {
|
|
super(quote(leftNode.toString()) + " -> " + quote(rightNode.toString()));
|
|
}
|
|
}
|
|
|
|
static class NodesGraph {
|
|
private final DotGraph graph =
|
|
new DotGraph("digraph")
|
|
.add(
|
|
new DotStatement<>("graph")
|
|
.addAttribute("rankdir", "LR")
|
|
.addAttribute("labeljust", "l")
|
|
.addAttribute("compound", true));
|
|
|
|
private final BindingGraph bindingGraph;
|
|
private final Map<Node, UUID> nodeIds = new HashMap<>();
|
|
|
|
NodesGraph(BindingGraph bindingGraph) {
|
|
this.bindingGraph = bindingGraph;
|
|
}
|
|
|
|
DotGraph graph() {
|
|
if (nodeIds.isEmpty()) {
|
|
Iterator<String> colors = Iterators.cycle(COMPONENT_COLORS);
|
|
bindingGraph.network().nodes().stream()
|
|
.collect(groupingBy(Node::componentPath))
|
|
.forEach(
|
|
(component, networkNodes) -> {
|
|
DotGraph subgraph = subgraph(component);
|
|
subgraph.add(
|
|
new DotStatement<>("node")
|
|
.addAttribute("style", "filled")
|
|
.addAttribute("shape", "box")
|
|
.addAttribute("fillcolor", colors.next()));
|
|
subgraph.add(new DotStatement<>("graph").addAttribute("label", component));
|
|
for (Node node : networkNodes) {
|
|
subgraph.add(dotNode(node));
|
|
}
|
|
});
|
|
for (Edge edge : bindingGraph.network().edges()) {
|
|
dotEdge(edge).ifPresent(graph::add);
|
|
}
|
|
}
|
|
return graph;
|
|
}
|
|
|
|
DotGraph subgraph(ComponentPath component) {
|
|
DotGraph subgraph = new DotGraph("subgraph " + quote(clusterName(component)));
|
|
graph.add(subgraph);
|
|
return subgraph;
|
|
}
|
|
|
|
UUID nodeId(Node node) {
|
|
return nodeIds.computeIfAbsent(node, n -> randomUUID());
|
|
}
|
|
|
|
Optional<DotEdge> dotEdge(Edge edge) {
|
|
EndpointPair<Node> incidentNodes = bindingGraph.network().incidentNodes(edge);
|
|
DotEdge dotEdge = new DotEdge(nodeId(incidentNodes.source()), nodeId(incidentNodes.target()));
|
|
if (edge instanceof DependencyEdge) {
|
|
if (((DependencyEdge) edge).isEntryPoint()) {
|
|
return Optional.empty();
|
|
}
|
|
} else if (edge instanceof ChildFactoryMethodEdge) {
|
|
dotEdge.addAttribute("style", "dashed");
|
|
dotEdge.addAttribute("lhead", clusterName(incidentNodes.target().componentPath()));
|
|
dotEdge.addAttribute("ltail", clusterName(incidentNodes.source().componentPath()));
|
|
dotEdge.addAttribute("taillabel", ((ChildFactoryMethodEdge) edge).factoryMethod());
|
|
} else if (edge instanceof SubcomponentCreatorBindingEdge) {
|
|
dotEdge.addAttribute("style", "dashed");
|
|
dotEdge.addAttribute("lhead", clusterName(incidentNodes.target().componentPath()));
|
|
dotEdge.addAttribute("taillabel", "subcomponent");
|
|
}
|
|
return Optional.of(dotEdge);
|
|
}
|
|
|
|
DotNode dotNode(Node node) {
|
|
DotNode dotNode = new DotNode(nodeId(node));
|
|
if (node instanceof MaybeBinding) {
|
|
dotNode.addAttribute("tooltip", "");
|
|
if (bindingGraph.entryPointBindings().contains(node)) {
|
|
dotNode.addAttribute("penwidth", 3);
|
|
}
|
|
if (node instanceof Binding) {
|
|
dotNode.addAttribute("label", label((Binding) node));
|
|
}
|
|
if (node instanceof MissingBinding) {
|
|
dotNode.addAttributeFormat(
|
|
"label", "missing binding for %s", ((MissingBinding) node).key());
|
|
}
|
|
} else {
|
|
dotNode.addAttribute("style", "invis").addAttribute("shape", "point");
|
|
}
|
|
return dotNode;
|
|
}
|
|
|
|
private String label(Binding binding) {
|
|
if (binding.kind().equals(BindingKind.MEMBERS_INJECTION)) {
|
|
return String.format("inject(%s)", binding.key());
|
|
} else if (binding.isProduction()) {
|
|
return String.format("@Produces %s", binding.key());
|
|
} else {
|
|
return binding.key().toString();
|
|
}
|
|
}
|
|
|
|
private static String clusterName(ComponentPath owningComponentPath) {
|
|
return "cluster" + owningComponentPath;
|
|
}
|
|
}
|
|
}
|