• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2008 Google Inc.
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 com.google.inject.grapher.graphviz;
18 
19 import com.google.common.base.Joiner;
20 import com.google.common.collect.ImmutableList;
21 import com.google.common.collect.Lists;
22 import com.google.common.collect.Maps;
23 import com.google.inject.Inject;
24 import com.google.inject.Key;
25 import com.google.inject.grapher.AbstractInjectorGrapher;
26 import com.google.inject.grapher.BindingEdge;
27 import com.google.inject.grapher.DependencyEdge;
28 import com.google.inject.grapher.ImplementationNode;
29 import com.google.inject.grapher.InstanceNode;
30 import com.google.inject.grapher.InterfaceNode;
31 import com.google.inject.grapher.NameFactory;
32 import com.google.inject.grapher.NodeId;
33 import com.google.inject.spi.InjectionPoint;
34 import java.io.PrintWriter;
35 import java.lang.reflect.Member;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Map.Entry;
39 
40 /**
41  * {@link com.google.inject.grapher.InjectorGrapher} implementation that writes out a Graphviz DOT
42  * file of the graph. Dependencies are bound in {@link GraphvizModule}.
43  * <p>
44  * Specify the {@link PrintWriter} to output to with {@link #setOut(PrintWriter)}.
45  *
46  * @author phopkins@gmail.com (Pete Hopkins)
47  * @since 4.0
48  */
49 public class GraphvizGrapher extends AbstractInjectorGrapher {
50   private final Map<NodeId, GraphvizNode> nodes = Maps.newHashMap();
51   private final List<GraphvizEdge> edges = Lists.newArrayList();
52   private final NameFactory nameFactory;
53   private final PortIdFactory portIdFactory;
54 
55   private PrintWriter out;
56   private String rankdir = "TB";
57 
GraphvizGrapher(@raphviz NameFactory nameFactory, @Graphviz PortIdFactory portIdFactory)58   @Inject GraphvizGrapher(@Graphviz NameFactory nameFactory,
59       @Graphviz PortIdFactory portIdFactory) {
60     this.nameFactory = nameFactory;
61     this.portIdFactory = portIdFactory;
62   }
63 
reset()64   @Override protected void reset() {
65     nodes.clear();
66     edges.clear();
67   }
68 
setOut(PrintWriter out)69   public void setOut(PrintWriter out) {
70     this.out = out;
71   }
72 
setRankdir(String rankdir)73   public void setRankdir(String rankdir) {
74     this.rankdir = rankdir;
75   }
76 
postProcess()77   @Override protected void postProcess() {
78     start();
79 
80     for (GraphvizNode node : nodes.values()) {
81       renderNode(node);
82     }
83 
84     for (GraphvizEdge edge : edges) {
85       renderEdge(edge);
86     }
87 
88     finish();
89 
90     out.flush();
91   }
92 
getGraphAttributes()93   protected Map<String, String> getGraphAttributes() {
94     Map<String, String> attrs = Maps.newHashMap();
95     attrs.put("rankdir", rankdir);
96     return attrs;
97   }
98 
start()99   protected void start() {
100     out.println("digraph injector {");
101 
102     Map<String, String> attrs = getGraphAttributes();
103     out.println("graph " + getAttrString(attrs) + ";");
104   }
105 
finish()106   protected void finish() {
107     out.println("}");
108   }
109 
renderNode(GraphvizNode node)110   protected void renderNode(GraphvizNode node) {
111     Map<String, String> attrs = getNodeAttributes(node);
112     out.println(node.getIdentifier() + " " + getAttrString(attrs));
113   }
114 
getNodeAttributes(GraphvizNode node)115   protected Map<String, String> getNodeAttributes(GraphvizNode node) {
116     Map<String, String> attrs = Maps.newHashMap();
117 
118     attrs.put("label", getNodeLabel(node));
119     // remove most of the margin because the table has internal padding
120     attrs.put("margin", "\"0.02,0\"");
121     attrs.put("shape", node.getShape().toString());
122     attrs.put("style", node.getStyle().toString());
123 
124     return attrs;
125   }
126 
127   /**
128    * Creates the "label" for a node. This is a string of HTML that defines a
129    * table with a heading at the top and (in the case of
130    * {@link ImplementationNode}s) rows for each of the member fields.
131    */
getNodeLabel(GraphvizNode node)132   protected String getNodeLabel(GraphvizNode node) {
133     String cellborder = node.getStyle() == NodeStyle.INVISIBLE ? "1" : "0";
134 
135     StringBuilder html = new StringBuilder();
136     html.append("<");
137     html.append("<table cellspacing=\"0\" cellpadding=\"5\" cellborder=\"");
138     html.append(cellborder).append("\" border=\"0\">");
139 
140     html.append("<tr>").append("<td align=\"left\" port=\"header\" ");
141     html.append("bgcolor=\"" + node.getHeaderBackgroundColor() + "\">");
142 
143     String subtitle = Joiner.on("<br align=\"left\"/>").join(node.getSubtitles());
144     if (subtitle.length() != 0) {
145       html.append("<font color=\"").append(node.getHeaderTextColor());
146       html.append("\" point-size=\"10\">");
147       html.append(subtitle).append("<br align=\"left\"/>").append("</font>");
148     }
149 
150     html.append("<font color=\"" + node.getHeaderTextColor() + "\">");
151     html.append(htmlEscape(node.getTitle())).append("<br align=\"left\"/>");
152     html.append("</font>").append("</td>").append("</tr>");
153 
154     for (Map.Entry<String, String> field : node.getFields().entrySet()) {
155       html.append("<tr>");
156       html.append("<td align=\"left\" port=\"").append(htmlEscape(field.getKey())).append("\">");
157       html.append(htmlEscape(field.getValue()));
158       html.append("</td>").append("</tr>");
159     }
160 
161     html.append("</table>");
162     html.append(">");
163     return html.toString();
164   }
165 
renderEdge(GraphvizEdge edge)166   protected void renderEdge(GraphvizEdge edge) {
167     Map<String, String> attrs = getEdgeAttributes(edge);
168 
169     String tailId = getEdgeEndPoint(nodes.get(edge.getTailNodeId()).getIdentifier(),
170         edge.getTailPortId(), edge.getTailCompassPoint());
171 
172     String headId = getEdgeEndPoint(nodes.get(edge.getHeadNodeId()).getIdentifier(),
173         edge.getHeadPortId(), edge.getHeadCompassPoint());
174 
175     out.println(tailId + " -> " + headId + " " + getAttrString(attrs));
176   }
177 
getEdgeAttributes(GraphvizEdge edge)178   protected Map<String, String> getEdgeAttributes(GraphvizEdge edge) {
179     Map<String, String> attrs = Maps.newHashMap();
180 
181     attrs.put("arrowhead", getArrowString(edge.getArrowHead()));
182     attrs.put("arrowtail", getArrowString(edge.getArrowTail()));
183     attrs.put("style", edge.getStyle().toString());
184 
185     return attrs;
186   }
187 
getAttrString(Map<String, String> attrs)188   private String getAttrString(Map<String, String> attrs) {
189     List<String> attrList = Lists.newArrayList();
190 
191     for (Entry<String, String> attr : attrs.entrySet()) {
192       String value = attr.getValue();
193 
194       if (value != null) {
195         attrList.add(attr.getKey() + "=" + value);
196       }
197     }
198 
199     return "[" + Joiner.on(", ").join(attrList) + "]";
200   }
201 
202   /**
203    * Turns a {@link List} of {@link ArrowType}s into a {@link String} that
204    * represents combining them. With Graphviz, that just means concatenating
205    * them.
206    */
getArrowString(List<ArrowType> arrows)207   protected String getArrowString(List<ArrowType> arrows) {
208     return Joiner.on("").join(arrows);
209   }
210 
getEdgeEndPoint(String nodeId, String portId, CompassPoint compassPoint)211   protected String getEdgeEndPoint(String nodeId, String portId, CompassPoint compassPoint) {
212     List<String> portStrings = Lists.newArrayList(nodeId);
213 
214     if (portId != null) {
215       portStrings.add(portId);
216     }
217 
218     if (compassPoint != null) {
219       portStrings.add(compassPoint.toString());
220     }
221 
222     return Joiner.on(":").join(portStrings);
223   }
224 
htmlEscape(String str)225   protected String htmlEscape(String str) {
226     return str.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
227   }
228 
htmlEscape(List<String> elements)229   protected List<String> htmlEscape(List<String> elements) {
230     List<String> escaped = Lists.newArrayList();
231     for (String element : elements) {
232       escaped.add(htmlEscape(element));
233     }
234     return escaped;
235   }
236 
newInterfaceNode(InterfaceNode node)237   @Override protected void newInterfaceNode(InterfaceNode node) {
238     // TODO(phopkins): Show the Module on the graph, which comes from the
239     // class name when source is a StackTraceElement.
240 
241     NodeId nodeId = node.getId();
242     GraphvizNode gnode = new GraphvizNode(nodeId);
243     gnode.setStyle(NodeStyle.DASHED);
244     Key<?> key = nodeId.getKey();
245     gnode.setTitle(nameFactory.getClassName(key));
246     gnode.addSubtitle(0, nameFactory.getAnnotationName(key));
247     addNode(gnode);
248   }
249 
newImplementationNode(ImplementationNode node)250   @Override protected void newImplementationNode(ImplementationNode node) {
251     NodeId nodeId = node.getId();
252     GraphvizNode gnode = new GraphvizNode(nodeId);
253     gnode.setStyle(NodeStyle.SOLID);
254 
255     gnode.setHeaderBackgroundColor("#000000");
256     gnode.setHeaderTextColor("#ffffff");
257     gnode.setTitle(nameFactory.getClassName(nodeId.getKey()));
258 
259     for (Member member : node.getMembers()) {
260       gnode.addField(portIdFactory.getPortId(member), nameFactory.getMemberName(member));
261     }
262 
263     addNode(gnode);
264   }
265 
newInstanceNode(InstanceNode node)266   @Override protected void newInstanceNode(InstanceNode node) {
267     NodeId nodeId = node.getId();
268     GraphvizNode gnode = new GraphvizNode(nodeId);
269     gnode.setStyle(NodeStyle.SOLID);
270 
271     gnode.setHeaderBackgroundColor("#000000");
272     gnode.setHeaderTextColor("#ffffff");
273     gnode.setTitle(nameFactory.getClassName(nodeId.getKey()));
274 
275     gnode.addSubtitle(0, nameFactory.getSourceName(node.getSource()));
276 
277     gnode.setHeaderBackgroundColor("#aaaaaa");
278     gnode.setHeaderTextColor("#ffffff");
279     gnode.setTitle(nameFactory.getInstanceName(node.getInstance()));
280 
281     for (Member member : node.getMembers()) {
282       gnode.addField(portIdFactory.getPortId(member), nameFactory.getMemberName(member));
283     }
284 
285     addNode(gnode);
286   }
287 
newDependencyEdge(DependencyEdge edge)288   @Override protected void newDependencyEdge(DependencyEdge edge) {
289     GraphvizEdge gedge = new GraphvizEdge(edge.getFromId(), edge.getToId());
290     InjectionPoint fromPoint = edge.getInjectionPoint();
291     if (fromPoint == null) {
292       gedge.setTailPortId("header");
293     } else {
294       gedge.setTailPortId(portIdFactory.getPortId(fromPoint.getMember()));
295     }
296     gedge.setArrowHead(ImmutableList.of(ArrowType.NORMAL));
297     gedge.setTailCompassPoint(CompassPoint.EAST);
298 
299     edges.add(gedge);
300   }
301 
newBindingEdge(BindingEdge edge)302   @Override protected void newBindingEdge(BindingEdge edge) {
303     GraphvizEdge gedge = new GraphvizEdge(edge.getFromId(), edge.getToId());
304     gedge.setStyle(EdgeStyle.DASHED);
305     switch (edge.getType()) {
306       case NORMAL:
307         gedge.setArrowHead(ImmutableList.of(ArrowType.NORMAL_OPEN));
308         break;
309 
310       case PROVIDER:
311         gedge.setArrowHead(ImmutableList.of(ArrowType.NORMAL_OPEN, ArrowType.NORMAL_OPEN));
312         break;
313 
314       case CONVERTED_CONSTANT:
315         gedge.setArrowHead(ImmutableList.of(ArrowType.NORMAL_OPEN, ArrowType.DOT_OPEN));
316         break;
317     }
318 
319     edges.add(gedge);
320   }
321 
addNode(GraphvizNode node)322   private void addNode(GraphvizNode node) {
323     node.setIdentifier("x" + nodes.size());
324     nodes.put(node.getNodeId(), node);
325   }
326 }
327