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