View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.util.designer;
5   
6   import java.awt.BorderLayout;
7   import java.awt.Color;
8   import java.awt.Dimension;
9   import java.awt.FontMetrics;
10  import java.awt.Graphics;
11  import java.util.List;
12  
13  import javax.swing.BorderFactory;
14  import javax.swing.DefaultListModel;
15  import javax.swing.JComponent;
16  import javax.swing.JList;
17  import javax.swing.JPanel;
18  import javax.swing.JScrollPane;
19  import javax.swing.ListSelectionModel;
20  import javax.swing.SwingConstants;
21  import javax.swing.SwingUtilities;
22  import javax.swing.event.ListSelectionEvent;
23  import javax.swing.event.ListSelectionListener;
24  
25  import net.sourceforge.pmd.lang.ast.Node;
26  import net.sourceforge.pmd.lang.dfa.DFAGraphMethod;
27  import net.sourceforge.pmd.lang.dfa.DataFlowNode;
28  import net.sourceforge.pmd.lang.dfa.VariableAccess;
29  import net.sourceforge.pmd.util.StringUtil;
30  
31  public class DFAPanel extends JComponent implements ListSelectionListener {
32  
33      public static class DFACanvas extends JPanel {
34  
35  	private static final int NODE_RADIUS = 12;
36  	private static final int NODE_DIAMETER = 2 * NODE_RADIUS;
37  
38  	private Node node;
39  
40  	private int x = 150;
41  	private int y = 50;
42  	private LineGetter lines;
43  
44  	private void addAccessLabel(StringBuffer sb, VariableAccess va) {
45  
46  	    if (va.isDefinition()) {
47  		sb.append("d(");
48  	    } else if (va.isReference()) {
49  		sb.append("r(");
50  	    } else if (va.isUndefinition()) {
51  		sb.append("u(");
52  		//continue;  // eo - the u() entries add a lot of clutter to the report
53  	    } else {
54  		sb.append("?(");
55  	    }
56  
57  	    sb.append(va.getVariableName()).append(')');
58  	}
59  
60  	private String childIndicesOf(DataFlowNode node, String separator) {
61  
62  	    List<DataFlowNode> kids = node.getChildren();
63  	    if (kids.isEmpty()) {
64  		return "";
65  	    }
66  
67  	    StringBuffer sb = new StringBuffer();
68  	    sb.append(kids.get(0).getIndex());
69  
70  	    for (int j = 1; j < node.getChildren().size(); j++) {
71  		sb.append(separator);
72  		sb.append(kids.get(j).getIndex());
73  	    }
74  	    return sb.toString();
75  	}
76  
77  	private String[] deriveAccessLabels(List<DataFlowNode> flow) {
78  
79  	    if (flow == null || flow.isEmpty()) {
80  		return StringUtil.EMPTY_STRINGS;
81  	    }
82  
83  	    String[] labels = new String[flow.size()];
84  
85  	    for (int i = 0; i < labels.length; i++) {
86  		List<VariableAccess> access = flow.get(i).getVariableAccess();
87  
88  		if (access == null || access.isEmpty()) {
89  		    continue; // leave a null at this slot
90  		}
91  
92  		StringBuffer exp = new StringBuffer();
93  		addAccessLabel(exp, access.get(0));
94  
95  		for (int k = 1; k < access.size(); k++) {
96  		    exp.append(", ");
97  		    addAccessLabel(exp, access.get(k));
98  		}
99  
100 		labels[i] = exp.toString();
101 	    }
102 	    return labels;
103 	}
104 
105 	private int maxWidthOf(String[] strings, FontMetrics fm) {
106 
107 	    int max = 0;
108 	    String str;
109 
110 	    for (String element : strings) {
111 		str = element;
112 		if (str == null) {
113 		    continue;
114 		}
115 		max = Math.max(max, SwingUtilities.computeStringWidth(fm, str));
116 	    }
117 	    return max;
118 	}
119 
120 	@Override
121 	public void paintComponent(Graphics g) {
122 	    super.paintComponent(g);
123 
124 	    if (node == null) {
125 		return;
126 	    }
127 
128 	    List<DataFlowNode> flow = node.getDataFlowNode().getFlow();
129 	    FontMetrics fm = g.getFontMetrics();
130 	    int halfFontHeight = fm.getAscent() / 2;
131 
132 	    String[] accessLabels = deriveAccessLabels(flow);
133 	    int maxAccessLabelWidth = maxWidthOf(accessLabels, fm);
134 
135 	    for (int i = 0; i < flow.size(); i++) {
136 		DataFlowNode inode = flow.get(i);
137 
138 		y = computeDrawPos(inode.getIndex());
139 
140 		g.drawArc(x, y, NODE_DIAMETER, NODE_DIAMETER, 0, 360);
141 		g.drawString(lines.getLine(inode.getLine()), x + 100 + maxAccessLabelWidth, y + 15);
142 
143 		// draw index number centered inside of node
144 		String idx = String.valueOf(inode.getIndex());
145 		int halfWidth = SwingUtilities.computeStringWidth(fm, idx) / 2;
146 		g.drawString(idx, x + NODE_RADIUS - halfWidth, y + NODE_RADIUS + halfFontHeight);
147 
148 		String accessLabel = accessLabels[i];
149 		if (accessLabel != null) {
150 		    g.drawString(accessLabel, x + 70, y + 15);
151 		}
152 
153 		for (int j = 0; j < inode.getChildren().size(); j++) {
154 		    DataFlowNode n = inode.getChildren().get(j);
155 		    drawMyLine(inode.getIndex(), n.getIndex(), g);
156 		}
157 		String childIndices = childIndicesOf(inode, ", ");
158 		g.drawString(childIndices, x - 3 * NODE_DIAMETER, y + NODE_RADIUS - 2);
159 	    }
160 	}
161 
162 	public void setCode(LineGetter h) {
163 	    this.lines = h;
164 	}
165 
166 	public void setMethod(Node node) {
167 	    this.node = node;
168 	}
169 
170 	private int computeDrawPos(int index) {
171 	    int z = NODE_RADIUS * 4;
172 	    return z + index * z;
173 	}
174 
175 	private void drawArrow(Graphics g, int x, int y, int direction) {
176 
177 	    final int height = NODE_RADIUS * 2 / 3;
178 	    final int width = NODE_RADIUS * 2 / 3;
179 
180 	    switch (direction) {
181 	    case SwingConstants.NORTH:
182 		g.drawLine(x, y, x - width / 2, y + height);
183 		g.drawLine(x, y, x + width / 2, y + height);
184 		break;
185 	    case SwingConstants.SOUTH:
186 		g.drawLine(x, y, x - width / 2, y - height);
187 		g.drawLine(x, y, x + width / 2, y - height);
188 		break;
189 	    case SwingConstants.EAST:
190 		g.drawLine(x, y, x - height, y - width / 2);
191 		g.drawLine(x, y, x - height, y + width / 2);
192 		break;
193 	    case SwingConstants.WEST:
194 		g.drawLine(x, y, x + height, y - width / 2);
195 		g.drawLine(x, y, x + height, y + width / 2);
196 		break;
197 	    default:
198 		// Do nothing
199 		break;
200 	    }
201 	}
202 
203 	private void drawMyLine(int index1, int index2, Graphics g) {
204 	    int y1 = this.computeDrawPos(index1);
205 	    int y2 = this.computeDrawPos(index2);
206 
207 	    //int arrow = 6;
208 
209 	    if (index1 < index2) {
210 		if (index2 - index1 == 1) {
211 		    x += NODE_RADIUS;
212 		    g.drawLine(x, y1 + NODE_DIAMETER, x, y2);
213 		    //  g.fillRect(x - arrow, y2 - arrow, arrow * 2, arrow * 2);
214 		    drawArrow(g, x, y2, SwingConstants.SOUTH);
215 		    x -= NODE_RADIUS;
216 		} else if (index2 - index1 > 1) {
217 		    y1 = y1 + NODE_RADIUS;
218 		    y2 = y2 + NODE_RADIUS;
219 		    int n = (index2 - index1 - 2) * 10 + 10;
220 		    g.drawLine(x, y1, x - n, y1);
221 		    g.drawLine(x - n, y1, x - n, y2);
222 		    g.drawLine(x - n, y2, x, y2);
223 		    //   g.fillRect(x - arrow, y2 - arrow, arrow * 2, arrow * 2);
224 		    drawArrow(g, x, y2, SwingConstants.EAST);
225 		}
226 
227 	    } else {
228 		if (index1 - index2 > 1) {
229 		    y1 = y1 + NODE_RADIUS;
230 		    y2 = y2 + NODE_RADIUS;
231 		    x = x + NODE_DIAMETER;
232 		    int n = (index1 - index2 - 2) * 10 + 10;
233 		    g.drawLine(x, y1, x + n, y1);
234 		    g.drawLine(x + n, y1, x + n, y2);
235 		    g.drawLine(x + n, y2, x, y2);
236 		    //      g.fillRect(x - arrow, y2 - arrow, arrow * 2, arrow * 2);
237 		    drawArrow(g, x, y2, SwingConstants.WEST);
238 		    x = x - NODE_DIAMETER;
239 		} else if (index1 - index2 == 1) {
240 		    y2 = y2 + NODE_DIAMETER;
241 		    g.drawLine(x + NODE_RADIUS, y2, x + NODE_RADIUS, y1);
242 		    //   g.fillRect(x + NODE_RADIUS - arrow, y2 - arrow, arrow * 2, arrow * 2);
243 		    drawArrow(g, x + NODE_RADIUS, y2, SwingConstants.NORTH);
244 		}
245 	    }
246 	}
247     }
248 
249     private static class ElementWrapper {
250 	private DFAGraphMethod node;
251 
252 	public ElementWrapper(DFAGraphMethod node) {
253 	    this.node = node;
254 	}
255 
256 	public DFAGraphMethod getNode() {
257 	    return node;
258 	}
259 
260 	@Override
261 	public String toString() {
262 	    return node.getName();
263 	}
264     }
265 
266     private DFACanvas dfaCanvas;
267     private JList nodeList;
268     private DefaultListModel nodes = new DefaultListModel();
269 
270     public DFAPanel() {
271 	super();
272 
273 	setLayout(new BorderLayout());
274 	JPanel leftPanel = new JPanel();
275 
276 	nodeList = new JList(nodes);
277 	nodeList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
278 	nodeList.setFixedCellWidth(150);
279 	nodeList.setBorder(BorderFactory.createLineBorder(Color.black));
280 	nodeList.addListSelectionListener(this);
281 
282 	leftPanel.add(nodeList);
283 	add(leftPanel, BorderLayout.WEST);
284 
285 	dfaCanvas = new DFACanvas();
286 	dfaCanvas.setBackground(Color.WHITE);
287 	dfaCanvas.setPreferredSize(new Dimension(900, 1400));
288 
289 	JScrollPane scrollPane = new JScrollPane(dfaCanvas);
290 
291 	add(scrollPane, BorderLayout.CENTER);
292     }
293 
294     public void valueChanged(ListSelectionEvent event) {
295 	ElementWrapper wrapper = null;
296 	if (nodes.size() == 1) {
297 	    wrapper = (ElementWrapper) nodes.get(0);
298 	} else if (nodes.isEmpty()) {
299 	    return;
300 	} else if (nodeList.getSelectedValue() == null) {
301 	    wrapper = (ElementWrapper) nodes.get(0);
302 	} else {
303 	    wrapper = (ElementWrapper) nodeList.getSelectedValue();
304 	}
305 	dfaCanvas.setMethod(wrapper.getNode());
306 	dfaCanvas.repaint();
307     }
308 
309     public void resetTo(List<DFAGraphMethod> newNodes, LineGetter lines) {
310 	dfaCanvas.setCode(lines);
311 	nodes.clear();
312 	for (DFAGraphMethod md : newNodes) {
313 	    nodes.addElement(new ElementWrapper(md));
314 	}
315 	nodeList.setSelectedIndex(0);
316 	dfaCanvas.setMethod(newNodes.get(0));
317 	repaint();
318     }
319 }