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
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;
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
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
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
205
206 if (index1 < index2) {
207 if (index2 - index1 == 1) {
208 x += NODE_RADIUS;
209 g.drawLine(x, y1 + NODE_DIAMETER, x, y2);
210
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
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
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
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 }