/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.util.designer; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; import java.util.List; import javax.swing.BorderFactory; import javax.swing.DefaultListModel; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.dfa.DFAGraphMethod; import net.sourceforge.pmd.lang.dfa.DataFlowNode; import net.sourceforge.pmd.lang.dfa.VariableAccess; import net.sourceforge.pmd.util.StringUtil; public class DFAPanel extends JComponent implements ListSelectionListener { public static class DFACanvas extends JPanel { private static final int NODE_RADIUS = 12; private static final int NODE_DIAMETER = 2 * NODE_RADIUS; private Node node; private int x = 150; private int y = 50; private LineGetter lines; private void addAccessLabel(StringBuffer sb, VariableAccess va) { if (va.isDefinition()) { sb.append("d("); } else if (va.isReference()) { sb.append("r("); } else if (va.isUndefinition()) { sb.append("u("); // continue; // eo - the u() entries add a lot of clutter to the // report } else { sb.append("?("); } sb.append(va.getVariableName()).append(')'); } private String childIndicesOf(DataFlowNode node, String separator) { List<DataFlowNode> kids = node.getChildren(); if (kids.isEmpty()) { return ""; } StringBuffer sb = new StringBuffer(); sb.append(kids.get(0).getIndex()); for (int j = 1; j < node.getChildren().size(); j++) { sb.append(separator); sb.append(kids.get(j).getIndex()); } return sb.toString(); } private String[] deriveAccessLabels(List<DataFlowNode> flow) { if (flow == null || flow.isEmpty()) { return StringUtil.getEmptyStrings(); } String[] labels = new String[flow.size()]; for (int i = 0; i < labels.length; i++) { List<VariableAccess> access = flow.get(i).getVariableAccess(); if (access == null || access.isEmpty()) { continue; // leave a null at this slot } StringBuffer exp = new StringBuffer(); addAccessLabel(exp, access.get(0)); for (int k = 1; k < access.size(); k++) { exp.append(", "); addAccessLabel(exp, access.get(k)); } labels[i] = exp.toString(); } return labels; } private int maxWidthOf(String[] strings, FontMetrics fm) { int max = 0; String str; for (String element : strings) { str = element; if (str == null) { continue; } max = Math.max(max, SwingUtilities.computeStringWidth(fm, str)); } return max; } @Override public void paintComponent(Graphics g) { super.paintComponent(g); if (node == null) { return; } List<DataFlowNode> flow = node.getDataFlowNode().getFlow(); FontMetrics fm = g.getFontMetrics(); int halfFontHeight = fm.getAscent() / 2; String[] accessLabels = deriveAccessLabels(flow); int maxAccessLabelWidth = maxWidthOf(accessLabels, fm); for (int i = 0; i < flow.size(); i++) { DataFlowNode inode = flow.get(i); y = computeDrawPos(inode.getIndex()); g.drawArc(x, y, NODE_DIAMETER, NODE_DIAMETER, 0, 360); g.drawString(lines.getLine(inode.getLine()), x + 100 + maxAccessLabelWidth, y + 15); // draw index number centered inside of node String idx = String.valueOf(inode.getIndex()); int halfWidth = SwingUtilities.computeStringWidth(fm, idx) / 2; g.drawString(idx, x + NODE_RADIUS - halfWidth, y + NODE_RADIUS + halfFontHeight); String accessLabel = accessLabels[i]; if (accessLabel != null) { g.drawString(accessLabel, x + 70, y + 15); } for (int j = 0; j < inode.getChildren().size(); j++) { DataFlowNode n = inode.getChildren().get(j); drawMyLine(inode.getIndex(), n.getIndex(), g); } String childIndices = childIndicesOf(inode, ", "); g.drawString(childIndices, x - 3 * NODE_DIAMETER, y + NODE_RADIUS - 2); } } public void setCode(LineGetter h) { this.lines = h; } public void setMethod(Node node) { this.node = node; } private int computeDrawPos(int index) { int z = NODE_RADIUS * 4; return z + index * z; } private void drawArrow(Graphics g, int x, int y, int direction) { final int height = NODE_RADIUS * 2 / 3; final int width = NODE_RADIUS * 2 / 3; switch (direction) { case SwingConstants.NORTH: g.drawLine(x, y, x - width / 2, y + height); g.drawLine(x, y, x + width / 2, y + height); break; case SwingConstants.SOUTH: g.drawLine(x, y, x - width / 2, y - height); g.drawLine(x, y, x + width / 2, y - height); break; case SwingConstants.EAST: g.drawLine(x, y, x - height, y - width / 2); g.drawLine(x, y, x - height, y + width / 2); break; case SwingConstants.WEST: g.drawLine(x, y, x + height, y - width / 2); g.drawLine(x, y, x + height, y + width / 2); break; default: // Do nothing break; } } private void drawMyLine(int index1, int index2, Graphics g) { int y1 = this.computeDrawPos(index1); int y2 = this.computeDrawPos(index2); if (index1 < index2) { if (index2 - index1 == 1) { x += NODE_RADIUS; g.drawLine(x, y1 + NODE_DIAMETER, x, y2); drawArrow(g, x, y2, SwingConstants.SOUTH); x -= NODE_RADIUS; } else if (index2 - index1 > 1) { y1 = y1 + NODE_RADIUS; y2 = y2 + NODE_RADIUS; int n = (index2 - index1 - 2) * 10 + 10; g.drawLine(x, y1, x - n, y1); g.drawLine(x - n, y1, x - n, y2); g.drawLine(x - n, y2, x, y2); drawArrow(g, x, y2, SwingConstants.EAST); } } else { if (index1 - index2 > 1) { y1 = y1 + NODE_RADIUS; y2 = y2 + NODE_RADIUS; x = x + NODE_DIAMETER; int n = (index1 - index2 - 2) * 10 + 10; g.drawLine(x, y1, x + n, y1); g.drawLine(x + n, y1, x + n, y2); g.drawLine(x + n, y2, x, y2); drawArrow(g, x, y2, SwingConstants.WEST); x = x - NODE_DIAMETER; } else if (index1 - index2 == 1) { y2 = y2 + NODE_DIAMETER; g.drawLine(x + NODE_RADIUS, y2, x + NODE_RADIUS, y1); drawArrow(g, x + NODE_RADIUS, y2, SwingConstants.NORTH); } } } } private static class ElementWrapper { private DFAGraphMethod node; ElementWrapper(DFAGraphMethod node) { this.node = node; } public DFAGraphMethod getNode() { return node; } @Override public String toString() { return node.getName(); } } private DFACanvas dfaCanvas; private JList nodeList; private DefaultListModel nodes = new DefaultListModel(); public DFAPanel() { super(); setLayout(new BorderLayout()); JPanel leftPanel = new JPanel(); nodeList = new JList(nodes); nodeList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); nodeList.setFixedCellWidth(150); nodeList.setBorder(BorderFactory.createLineBorder(Color.black)); nodeList.addListSelectionListener(this); leftPanel.add(nodeList); add(leftPanel, BorderLayout.WEST); dfaCanvas = new DFACanvas(); dfaCanvas.setBackground(Color.WHITE); dfaCanvas.setPreferredSize(new Dimension(900, 1400)); JScrollPane scrollPane = new JScrollPane(dfaCanvas); add(scrollPane, BorderLayout.CENTER); } @Override public void valueChanged(ListSelectionEvent event) { ElementWrapper wrapper = null; if (nodes.size() == 1) { wrapper = (ElementWrapper) nodes.get(0); } else if (nodes.isEmpty()) { return; } else if (nodeList.getSelectedValue() == null) { wrapper = (ElementWrapper) nodes.get(0); } else { wrapper = (ElementWrapper) nodeList.getSelectedValue(); } dfaCanvas.setMethod(wrapper.getNode()); dfaCanvas.repaint(); } public void resetTo(List<DFAGraphMethod> newNodes, LineGetter lines) { dfaCanvas.setCode(lines); nodes.clear(); for (DFAGraphMethod md : newNodes) { nodes.addElement(new ElementWrapper(md)); } nodeList.setSelectedIndex(0); dfaCanvas.setMethod(newNodes.get(0)); repaint(); } }