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