xref: /aosp_15_r20/external/dagger2/java/dagger/example/spi/BindingGraphVisualizer.java (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
1 /*
2  * Copyright (C) 2017 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.example.spi;
18 
19 import static java.util.UUID.randomUUID;
20 import static java.util.regex.Matcher.quoteReplacement;
21 import static java.util.stream.Collectors.groupingBy;
22 
23 import com.google.auto.service.AutoService;
24 import com.google.common.base.Joiner;
25 import com.google.common.base.Strings;
26 import com.google.common.collect.ImmutableList;
27 import com.google.common.collect.Iterators;
28 import com.google.common.graph.EndpointPair;
29 import com.google.errorprone.annotations.CanIgnoreReturnValue;
30 import com.squareup.javapoet.ClassName;
31 import dagger.model.Binding;
32 import dagger.model.BindingGraph;
33 import dagger.model.BindingGraph.ChildFactoryMethodEdge;
34 import dagger.model.BindingGraph.DependencyEdge;
35 import dagger.model.BindingGraph.Edge;
36 import dagger.model.BindingGraph.MaybeBinding;
37 import dagger.model.BindingGraph.MissingBinding;
38 import dagger.model.BindingGraph.Node;
39 import dagger.model.BindingGraph.SubcomponentCreatorBindingEdge;
40 import dagger.model.BindingKind;
41 import dagger.model.ComponentPath;
42 import dagger.spi.BindingGraphPlugin;
43 import dagger.spi.DiagnosticReporter;
44 import java.io.IOException;
45 import java.io.PrintWriter;
46 import java.util.ArrayList;
47 import java.util.HashMap;
48 import java.util.Iterator;
49 import java.util.LinkedHashMap;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Optional;
53 import java.util.UUID;
54 import java.util.stream.Collectors;
55 import javax.annotation.processing.Filer;
56 import javax.lang.model.element.TypeElement;
57 import javax.tools.FileObject;
58 import javax.tools.StandardLocation;
59 
60 /**
61  * Experimental visualizer used as a proof-of-concept for {@link BindingGraphPlugin}.
62  *
63  * <p>For each component, writes a <a href=http://www.graphviz.org/content/dot-language>DOT file</a>
64  * in the same package. The file name is the name of the component type (with enclosing type names,
65  * joined by underscores, preceding it), with a {@code .dot} extension.
66  *
67  * <p>For example, for a nested component type {@code Foo.Bar} this will generate a file {@code
68  * Foo_Bar.dot}.
69  */
70 @AutoService(BindingGraphPlugin.class)
71 public final class BindingGraphVisualizer implements BindingGraphPlugin {
72   private Filer filer;
73 
74   @Override
initFiler(Filer filer)75   public void initFiler(Filer filer) {
76     this.filer = filer;
77   }
78 
79   /** Graphviz color names to use for binding nodes within each component. */
80   private static final ImmutableList<String> COMPONENT_COLORS =
81       ImmutableList.of(
82           "/set312/1",
83           "/set312/2",
84           "/set312/3",
85           "/set312/4",
86           "/set312/5",
87           "/set312/6",
88           "/set312/7",
89           "/set312/8",
90           "/set312/9",
91           "/set312/10",
92           "/set312/11",
93           "/set312/12");
94 
95   @Override
visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter)96   public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {
97     TypeElement componentElement =
98         bindingGraph.rootComponentNode().componentPath().currentComponent();
99     DotGraph graph = new NodesGraph(bindingGraph).graph();
100     ClassName componentName = ClassName.get(componentElement);
101     try {
102       FileObject file =
103           filer
104               .createResource(
105                   StandardLocation.CLASS_OUTPUT,
106                   componentName.packageName(),
107                   Joiner.on('_').join(componentName.simpleNames()) + ".dot",
108                   componentElement);
109       try (PrintWriter writer = new PrintWriter(file.openWriter())) {
110         graph.write(0, writer);
111       }
112     } catch (IOException e) {
113       throw new RuntimeException(e);
114     }
115   }
116 
117   private abstract static class Indented {
118 
write(int level, PrintWriter writer)119     abstract void write(int level, PrintWriter writer);
120 
121     @CanIgnoreReturnValue
indent(int level, PrintWriter writer)122     PrintWriter indent(int level, PrintWriter writer) {
123       writer.print(Strings.repeat(" ", level * 2));
124       return writer;
125     }
126   }
127 
128   static class DotGraph extends Indented {
129     private final String header;
130     private final List<Indented> elements = new ArrayList<>();
131 
DotGraph(String header)132     DotGraph(String header) {
133       this.header = header;
134     }
135 
136     @CanIgnoreReturnValue
add(Indented element)137     DotGraph add(Indented element) {
138       elements.add(element);
139       return this;
140     }
141 
142     @Override
write(int level, PrintWriter writer)143     void write(int level, PrintWriter writer) {
144       indent(level, writer);
145       writer.println(header + " {");
146       for (Indented element : elements) {
147         element.write(level + 1, writer);
148       }
149       indent(level, writer);
150       writer.println("}");
151     }
152   }
153 
154   static class DotStatement<S extends DotStatement<S>> extends Indented {
155     private final String base;
156     private final Map<String, Object> attributes = new LinkedHashMap<>();
157 
DotStatement(String base)158     DotStatement(String base) {
159       this.base = base;
160     }
161 
162     @SuppressWarnings("unchecked")
163     @CanIgnoreReturnValue
addAttribute(String name, Object value)164     S addAttribute(String name, Object value) {
165       attributes.put(name, value);
166       return (S) this;
167     }
168 
169     @CanIgnoreReturnValue
addAttributeFormat(String name, String format, Object... args)170     S addAttributeFormat(String name, String format, Object... args) {
171       return addAttribute(name, String.format(format, args));
172     }
173 
174     @Override
write(int level, PrintWriter writer)175     void write(int level, PrintWriter writer) {
176       indent(level, writer);
177       writer.print(base);
178       if (!attributes.isEmpty()) {
179         writer.print(
180             attributes
181                 .entrySet()
182                 .stream()
183                 .map(
184                     entry ->
185                         String.format("%s=%s", entry.getKey(), quote(entry.getValue().toString())))
186                 .collect(Collectors.joining(", ", " [", "]")));
187       }
188       writer.println();
189     }
190   }
191 
quote(String string)192   private static String quote(String string) {
193     return '"' + string.replaceAll("\"", quoteReplacement("\\\"")) + '"';
194   }
195 
196   static class DotNode extends DotStatement<DotNode> {
DotNode(Object nodeName)197     DotNode(Object nodeName) {
198       super(quote(nodeName.toString()));
199     }
200   }
201 
202   static class DotEdge extends DotStatement<DotEdge> {
DotEdge(Object leftNode, Object rightNode)203     DotEdge(Object leftNode, Object rightNode) {
204       super(quote(leftNode.toString()) + " -> " + quote(rightNode.toString()));
205     }
206   }
207 
208   static class NodesGraph {
209     private final DotGraph graph =
210         new DotGraph("digraph")
211             .add(
212                 new DotStatement<>("graph")
213                     .addAttribute("rankdir", "LR")
214                     .addAttribute("labeljust", "l")
215                     .addAttribute("compound", true));
216 
217     private final BindingGraph bindingGraph;
218     private final Map<Node, UUID> nodeIds = new HashMap<>();
219 
NodesGraph(BindingGraph bindingGraph)220     NodesGraph(BindingGraph bindingGraph) {
221       this.bindingGraph = bindingGraph;
222     }
223 
graph()224     DotGraph graph() {
225       if (nodeIds.isEmpty()) {
226         Iterator<String> colors = Iterators.cycle(COMPONENT_COLORS);
227         bindingGraph.network().nodes().stream()
228             .collect(groupingBy(Node::componentPath))
229             .forEach(
230                 (component, networkNodes) -> {
231                   DotGraph subgraph = subgraph(component);
232                   subgraph.add(
233                       new DotStatement<>("node")
234                           .addAttribute("style", "filled")
235                           .addAttribute("shape", "box")
236                           .addAttribute("fillcolor", colors.next()));
237                   subgraph.add(new DotStatement<>("graph").addAttribute("label", component));
238                   for (Node node : networkNodes) {
239                     subgraph.add(dotNode(node));
240                   }
241                 });
242         for (Edge edge : bindingGraph.network().edges()) {
243           dotEdge(edge).ifPresent(graph::add);
244         }
245       }
246       return graph;
247     }
248 
subgraph(ComponentPath component)249     DotGraph subgraph(ComponentPath component) {
250       DotGraph subgraph = new DotGraph("subgraph " + quote(clusterName(component)));
251       graph.add(subgraph);
252       return subgraph;
253     }
254 
nodeId(Node node)255     UUID nodeId(Node node) {
256       return nodeIds.computeIfAbsent(node, n -> randomUUID());
257     }
258 
dotEdge(Edge edge)259     Optional<DotEdge> dotEdge(Edge edge) {
260       EndpointPair<Node> incidentNodes = bindingGraph.network().incidentNodes(edge);
261       DotEdge dotEdge = new DotEdge(nodeId(incidentNodes.source()), nodeId(incidentNodes.target()));
262       if (edge instanceof DependencyEdge) {
263         if (((DependencyEdge) edge).isEntryPoint()) {
264           return Optional.empty();
265         }
266       } else if (edge instanceof ChildFactoryMethodEdge) {
267         dotEdge.addAttribute("style", "dashed");
268         dotEdge.addAttribute("lhead", clusterName(incidentNodes.target().componentPath()));
269         dotEdge.addAttribute("ltail", clusterName(incidentNodes.source().componentPath()));
270         dotEdge.addAttribute("taillabel", ((ChildFactoryMethodEdge) edge).factoryMethod());
271       } else if (edge instanceof SubcomponentCreatorBindingEdge) {
272         dotEdge.addAttribute("style", "dashed");
273         dotEdge.addAttribute("lhead", clusterName(incidentNodes.target().componentPath()));
274         dotEdge.addAttribute("taillabel", "subcomponent");
275       }
276       return Optional.of(dotEdge);
277     }
278 
dotNode(Node node)279     DotNode dotNode(Node node) {
280       DotNode dotNode = new DotNode(nodeId(node));
281       if (node instanceof MaybeBinding) {
282         dotNode.addAttribute("tooltip", "");
283         if (bindingGraph.entryPointBindings().contains(node)) {
284           dotNode.addAttribute("penwidth", 3);
285         }
286         if (node instanceof Binding) {
287           dotNode.addAttribute("label", label((Binding) node));
288         }
289         if (node instanceof MissingBinding) {
290           dotNode.addAttributeFormat(
291               "label", "missing binding for %s", ((MissingBinding) node).key());
292         }
293       } else {
294         dotNode.addAttribute("style", "invis").addAttribute("shape", "point");
295       }
296       return dotNode;
297     }
298 
label(Binding binding)299     private String label(Binding binding) {
300       if (binding.kind().equals(BindingKind.MEMBERS_INJECTION)) {
301         return String.format("inject(%s)", binding.key());
302       } else if (binding.isProduction()) {
303         return String.format("@Produces %s", binding.key());
304       } else {
305         return binding.key().toString();
306       }
307     }
308 
clusterName(ComponentPath owningComponentPath)309     private static String clusterName(ComponentPath owningComponentPath) {
310       return "cluster" + owningComponentPath;
311     }
312   }
313 }
314