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