/*
* SonarQube Java
* Copyright (C) 2012-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* 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. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.java.cfg;
import org.sonar.java.cfg.CFG.Block;
import org.sonar.java.model.SyntaxTreeDebug;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.Tree.Kind;
import java.text.MessageFormat;
import java.util.List;
public class CFGDebug {
private static final String DOT_PADDING = " ";
private static final String NEW_LINE = "\n";
private static final int MAX_KINDNAME = Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT.name().length() + 5;
private CFGDebug() {
}
/**
* Convert the CFG to DOT format (graph description language).
* See language specification: http://www.graphviz.org/content/dot-language
*/
public static String toDot(CFG cfg) {
StringBuilder sb = new StringBuilder();
List<Block> blocks = cfg.blocks();
// nodes
int firstBlockId = blocks.size() - 1;
blocks.stream().map(block -> dotBlockLabel(block.id(), firstBlockId)).forEach(sb::append);
sb.append(NEW_LINE);
// edges
for (Block block : blocks) {
block.successors().stream().map(successor -> dotSuccessorFormat(block, successor)).forEach(sb::append);
block.exceptions().stream().map(exception -> dotExceptionFormat(block, exception)).forEach(sb::append);
}
return escapeNewLines("graph cfg {" + NEW_LINE + sb.toString() + "}");
}
private static String escapeNewLines(String dotGraph) {
return dotGraph.replaceAll("\\n", "\\\\n");
}
private static String dotBlockLabel(int blockId, int firstBlockId) {
String label = "B" + blockId;
String color = "";
if (blockId == 0) {
label += " (EXIT)";
color = "red";
} else if (blockId == firstBlockId) {
label += " (START)";
color = "green";
}
if (!color.isEmpty()) {
color = MessageFormat.format(",fillcolor=\"{0}\",fontcolor=\"white\"", color);
}
return MessageFormat.format(DOT_PADDING + "{0}[label=\"{1}\"{2}];" + NEW_LINE, blockId, label, color);
}
private static String dotSuccessorFormat(Block block, Block successor) {
return MessageFormat.format(DOT_PADDING + "{0}->{1}{2};" + NEW_LINE, block.id(), successor.id(), dotSuccessorLabel(block, successor));
}
private static String dotSuccessorLabel(Block block, Block successor) {
String edgeLabel = "";
if (successor == block.trueBlock()) {
edgeLabel = "TRUE";
} else if (successor == block.falseBlock()) {
edgeLabel = "FALSE";
} else if (successor == block.exitBlock()) {
edgeLabel = "EXIT";
}
if (!edgeLabel.isEmpty()) {
return "[label=\"" + edgeLabel + "\"]";
}
return "";
}
private static String dotExceptionFormat(Block block, Block exception) {
return MessageFormat.format(DOT_PADDING + "{0}->{1}[label=\"EXCEPTION\",color=\"orange\",fontcolor=\"orange\"];" + NEW_LINE, block.id(), exception.id());
}
public static String toString(CFG cfg) {
StringBuilder buffer = new StringBuilder();
buffer.append("Starts at B");
buffer.append(cfg.entry().id());
buffer.append('\n');
buffer.append('\n');
for (Block block : cfg.blocks()) {
buffer.append(toString(block));
}
return buffer.toString();
}
public static String toString(CFG.Block block) {
StringBuilder buffer = new StringBuilder();
buffer.append('B');
buffer.append(block.id());
if (block.id() == 0) {
buffer.append(" (Exit):");
}
int i = 0;
for (Tree tree : block.elements()) {
buffer.append('\n');
buffer.append(i);
buffer.append(":\t");
appendKind(buffer, tree.kind());
buffer.append(SyntaxTreeDebug.toString(tree));
i++;
}
Tree terminator = block.terminator();
if (terminator != null) {
buffer.append("\nT:\t");
appendKind(buffer, terminator.kind());
buffer.append(SyntaxTreeDebug.toString(terminator));
}
boolean first = true;
for (Block successor : block.successors()) {
if (first) {
first = false;
buffer.append('\n');
buffer.append("\tjumps to: ");
} else {
buffer.append(' ');
}
buffer.append('B');
buffer.append(successor.id());
if (successor == block.trueBlock()) {
buffer.append("(true)");
}
if (successor == block.falseBlock()) {
buffer.append("(false)");
}
if (successor == block.exitBlock()) {
buffer.append("(exit)");
}
}
first = true;
for (Block exception : block.exceptions()) {
if (first) {
first = false;
buffer.append('\n');
buffer.append("\texceptions to: ");
} else {
buffer.append(' ');
}
buffer.append('B');
buffer.append(exception.id());
}
buffer.append('\n');
buffer.append('\n');
return buffer.toString();
}
private static void appendKind(StringBuilder buffer, Kind kind) {
String name = kind.name();
int n = MAX_KINDNAME - name.length();
buffer.append(name);
while (--n >= 0) {
buffer.append(' ');
}
buffer.append('\t');
}
}