/*
* JCarder -- cards Java programs to keep threads disentangled
*
* Copyright (C) 2006-2007 Enea AB
* Copyright (C) 2007 Ulrik Svensson
* Copyright (C) 2007 Joel Rosdahl
*
* This program is made available under the GNU GPL version 2, with a special
* exception for linking with JUnit. See the accompanying file LICENSE.txt for
* details.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
package com.enea.jcarder.analyzer;
import java.util.HashSet;
import com.enea.jcarder.common.LockingContext;
import com.enea.jcarder.common.contexts.ContextReaderIfc;
/**
* This class can be used to generate a Graphviz <http://www.graphviz.org> graph
* as a string.
*
* TODO Add tooltips to the graph? The tooltips might for example contain the
* package names of classes.
*
* TODO If there are to many edges, merge them together in the graph and add an
* href link to an html page that describes them all?
*
* TODO Optionaly merge edges that are identical except for the threads?
*/
final class GraphvizGenerator {
private static final String EDGE_LABEL_FORMAT =
" [fontsize=10, label=<\n" +
" <table align=\"left\" border=\"0\" cellborder=\"0\"\n" +
" cellspacing=\"0\" cellpadding=\"0\">\n" +
" <tr>\n" +
" <td align=\"left\"> </td>\n" +
" <td align=\"left\" colspan=\"2\">" +
"Thread: %1$s<br align=\"left\"/>" +
"</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td align=\"left\"> </td>\n" +
" <td align=\"left\" colspan=\"2\">" +
"holding: %2$s<br align=\"left\"/>" +
"</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td align=\"left\"> </td>\n" +
" <td align=\"left\"> </td>\n" +
" <td align=\"left\">" +
"in: %3$s<br align=\"left\"/>" +
"</td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td align=\"left\"> </td>\n" +
" <td align=\"left\" colspan=\"2\">" +
"taking: %4$s<br align=\"left\"/>" +
"</td>\n" +
" </tr> \n" +
" <tr>\n" +
" <td align=\"left\"> </td>\n" +
" <td align=\"left\"> </td>\n" +
" <td align=\"left\">" +
"in: %5$s<br align=\"left\"/>" +
"</td>\n" +
" </tr>\n" +
" </table>\n" +
" >]";
public String generate(Iterable<LockEdge> edgesToBePrinted,
ContextReaderIfc reader,
boolean includePackages) {
StringBuffer sb = new StringBuffer();
sb.append("digraph G {\n");
sb.append(" node [shape=ellipse, style=filled, fontsize=12];\n");
final HashSet<LockNode> alreadyAppendedNodes = new HashSet<LockNode>();
for (LockEdge edge : edgesToBePrinted) {
appendNodeIfNotAppended(reader,
sb,
alreadyAppendedNodes,
edge.getSource());
appendNodeIfNotAppended(reader,
sb,
alreadyAppendedNodes,
edge.getTarget());
sb.append(" " + edge.getSource().toString() + "");
sb.append(" -> " + edge.getTarget().toString() + "");
sb.append(createEdgeLabel(reader, edge, includePackages));
sb.append(";\n");
}
sb.append("}\n");
return sb.toString();
}
private String createEdgeLabel(ContextReaderIfc reader,
LockEdge edge,
boolean includePackages) {
final LockingContext source =
reader.readContext(edge.getSourceLockingContextId());
final LockingContext target =
reader.readContext(edge.getTargetLockingContextId());
return String.format(EDGE_LABEL_FORMAT,
escape(handlePackage(target.getThreadName(),
includePackages)),
escape(handlePackage(source.getLockReference(),
includePackages)),
escape(handlePackage(source.getMethodWithClass(),
includePackages)),
escape(handlePackage(target.getLockReference(),
includePackages)),
escape(handlePackage(target.getMethodWithClass(),
includePackages)));
}
private String handlePackage(String s,
boolean includePackages) {
String[] parts = s.split("\\.");
if (parts.length >= 2 && !includePackages) {
return parts[parts.length - 2] + "." + parts[parts.length - 1];
} else {
return s;
}
}
private static String escape(String s) {
return s.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("\'", "'");
}
private String getLockNodeString(LockNode node,
ContextReaderIfc reader) {
final String color;
switch (node.getCycleType()) {
case CYCLE:
color = "firebrick1";
break;
case SINGLE_THREADED_CYCLE:
color = "yellow";
break;
default:
color = "white";
}
return " " + node.toString() + " [label = \""
+ escape(reader.readLock(node.getLockId()).toString())
+ "\" , fillcolor=" + color + "];\n";
}
private void appendNodeIfNotAppended(ContextReaderIfc reader,
StringBuffer sb,
HashSet<LockNode> alreadyAppendedNodes,
LockNode node) {
if (!alreadyAppendedNodes.contains(node)) {
alreadyAppendedNodes.add(node);
sb.append(getLockNodeString(node, reader));
}
}
}