View Javadoc

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