001 package net.sourceforge.pmd.util.designer;
002
003 import java.awt.BorderLayout;
004 import java.awt.Color;
005 import java.awt.Dimension;
006 import java.awt.FontMetrics;
007 import java.awt.Graphics;
008 import java.util.List;
009
010 import javax.swing.BorderFactory;
011 import javax.swing.DefaultListModel;
012 import javax.swing.JComponent;
013 import javax.swing.JList;
014 import javax.swing.JPanel;
015 import javax.swing.JScrollPane;
016 import javax.swing.ListSelectionModel;
017 import javax.swing.SwingConstants;
018 import javax.swing.SwingUtilities;
019 import javax.swing.event.ListSelectionEvent;
020 import javax.swing.event.ListSelectionListener;
021
022 import net.sourceforge.pmd.lang.ast.Node;
023 import net.sourceforge.pmd.lang.dfa.DataFlowNode;
024 import net.sourceforge.pmd.lang.dfa.VariableAccess;
025 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
026 import net.sourceforge.pmd.util.LineGetter;
027 import net.sourceforge.pmd.util.StringUtil;
028
029 public class DFAPanel extends JComponent implements ListSelectionListener {
030
031 public static class DFACanvas extends JPanel {
032
033 private static final int NODE_RADIUS = 12;
034 private static final int NODE_DIAMETER = 2 * NODE_RADIUS;
035
036 private Node node;
037
038 private int x = 150;
039 private int y = 50;
040 private LineGetter lines;
041
042 private void addAccessLabel(StringBuffer sb, VariableAccess va) {
043
044 if (va.isDefinition()) {
045 sb.append("d(");
046 } else if (va.isReference()) {
047 sb.append("r(");
048 } else if (va.isUndefinition()) {
049 sb.append("u(");
050 //continue; // eo - the u() entries add a lot of clutter to the report
051 } else {
052 sb.append("?(");
053 }
054
055 sb.append(va.getVariableName()).append(')');
056 }
057
058 private String childIndicesOf(DataFlowNode node, String separator) {
059
060 List<DataFlowNode> kids = node.getChildren();
061 if (kids.isEmpty()) {
062 return "";
063 }
064
065 StringBuffer sb = new StringBuffer();
066 sb.append(kids.get(0).getIndex());
067
068 for (int j = 1; j < node.getChildren().size(); j++) {
069 sb.append(separator);
070 sb.append(kids.get(j).getIndex());
071 }
072 return sb.toString();
073 }
074
075 private String[] deriveAccessLabels(List<DataFlowNode> flow) {
076
077 if (flow == null || flow.isEmpty()) {
078 return StringUtil.EMPTY_STRINGS;
079 }
080
081 String[] labels = new String[flow.size()];
082
083 for (int i = 0; i < labels.length; i++) {
084 List<VariableAccess> access = flow.get(i).getVariableAccess();
085
086 if (access == null || access.isEmpty()) {
087 continue; // leave a null at this slot
088 }
089
090 StringBuffer exp = new StringBuffer();
091 addAccessLabel(exp, access.get(0));
092
093 for (int k = 1; k < access.size(); k++) {
094 exp.append(", ");
095 addAccessLabel(exp, access.get(k));
096 }
097
098 labels[i] = exp.toString();
099 }
100 return labels;
101 }
102
103 private int maxWidthOf(String[] strings, FontMetrics fm) {
104
105 int max = 0;
106 String str;
107
108 for (String element : strings) {
109 str = element;
110 if (str == null) {
111 continue;
112 }
113 max = Math.max(max, SwingUtilities.computeStringWidth(fm, str));
114 }
115 return max;
116 }
117
118 @Override
119 public void paintComponent(Graphics g) {
120 super.paintComponent(g);
121
122 if (node == null) {
123 return;
124 }
125
126 List<DataFlowNode> flow = node.getDataFlowNode().getFlow();
127 FontMetrics fm = g.getFontMetrics();
128 int halfFontHeight = fm.getAscent() / 2;
129
130 String[] accessLabels = deriveAccessLabels(flow);
131 int maxAccessLabelWidth = maxWidthOf(accessLabels, fm);
132
133 for (int i = 0; i < flow.size(); i++) {
134 DataFlowNode inode = flow.get(i);
135
136 y = computeDrawPos(inode.getIndex());
137
138 g.drawArc(x, y, NODE_DIAMETER, NODE_DIAMETER, 0, 360);
139 g.drawString(lines.getLine(inode.getLine()), x + 100 + maxAccessLabelWidth, y + 15);
140
141 // draw index number centered inside of node
142 String idx = String.valueOf(inode.getIndex());
143 int halfWidth = SwingUtilities.computeStringWidth(fm, idx) / 2;
144 g.drawString(idx, x + NODE_RADIUS - halfWidth, y + NODE_RADIUS + halfFontHeight);
145
146 String accessLabel = accessLabels[i];
147 if (accessLabel != null) {
148 g.drawString(accessLabel, x + 70, y + 15);
149 }
150
151 for (int j = 0; j < inode.getChildren().size(); j++) {
152 DataFlowNode n = inode.getChildren().get(j);
153 drawMyLine(inode.getIndex(), n.getIndex(), g);
154 }
155 String childIndices = childIndicesOf(inode, ", ");
156 g.drawString(childIndices, x - 3 * NODE_DIAMETER, y + NODE_RADIUS - 2);
157 }
158 }
159
160 public void setCode(LineGetter h) {
161 this.lines = h;
162 }
163
164 public void setMethod(Node node) {
165 this.node = node;
166 }
167
168 private int computeDrawPos(int index) {
169 int z = NODE_RADIUS * 4;
170 return z + index * z;
171 }
172
173 private void drawArrow(Graphics g, int x, int y, int direction) {
174
175 final int height = NODE_RADIUS * 2 / 3;
176 final int width = NODE_RADIUS * 2 / 3;
177
178 switch (direction) {
179 case SwingConstants.NORTH:
180 g.drawLine(x, y, x - width / 2, y + height);
181 g.drawLine(x, y, x + width / 2, y + height);
182 break;
183 case SwingConstants.SOUTH:
184 g.drawLine(x, y, x - width / 2, y - height);
185 g.drawLine(x, y, x + width / 2, y - height);
186 break;
187 case SwingConstants.EAST:
188 g.drawLine(x, y, x - height, y - width / 2);
189 g.drawLine(x, y, x - height, y + width / 2);
190 break;
191 case SwingConstants.WEST:
192 g.drawLine(x, y, x + height, y - width / 2);
193 g.drawLine(x, y, x + height, y + width / 2);
194 break;
195 default:
196 // Do nothing
197 break;
198 }
199 }
200
201 private void drawMyLine(int index1, int index2, Graphics g) {
202 int y1 = this.computeDrawPos(index1);
203 int y2 = this.computeDrawPos(index2);
204
205 //int arrow = 6;
206
207 if (index1 < index2) {
208 if (index2 - index1 == 1) {
209 x += NODE_RADIUS;
210 g.drawLine(x, y1 + NODE_DIAMETER, x, y2);
211 // g.fillRect(x - arrow, y2 - arrow, arrow * 2, arrow * 2);
212 drawArrow(g, x, y2, SwingConstants.SOUTH);
213 x -= NODE_RADIUS;
214 } else if (index2 - index1 > 1) {
215 y1 = y1 + NODE_RADIUS;
216 y2 = y2 + NODE_RADIUS;
217 int n = (index2 - index1 - 2) * 10 + 10;
218 g.drawLine(x, y1, x - n, y1);
219 g.drawLine(x - n, y1, x - n, y2);
220 g.drawLine(x - n, y2, x, y2);
221 // g.fillRect(x - arrow, y2 - arrow, arrow * 2, arrow * 2);
222 drawArrow(g, x, y2, SwingConstants.EAST);
223 }
224
225 } else {
226 if (index1 - index2 > 1) {
227 y1 = y1 + NODE_RADIUS;
228 y2 = y2 + NODE_RADIUS;
229 x = x + NODE_DIAMETER;
230 int n = (index1 - index2 - 2) * 10 + 10;
231 g.drawLine(x, y1, x + n, y1);
232 g.drawLine(x + n, y1, x + n, y2);
233 g.drawLine(x + n, y2, x, y2);
234 // g.fillRect(x - arrow, y2 - arrow, arrow * 2, arrow * 2);
235 drawArrow(g, x, y2, SwingConstants.WEST);
236 x = x - NODE_DIAMETER;
237 } else if (index1 - index2 == 1) {
238 y2 = y2 + NODE_DIAMETER;
239 g.drawLine(x + NODE_RADIUS, y2, x + NODE_RADIUS, y1);
240 // g.fillRect(x + NODE_RADIUS - arrow, y2 - arrow, arrow * 2, arrow * 2);
241 drawArrow(g, x + NODE_RADIUS, y2, SwingConstants.NORTH);
242 }
243 }
244 }
245 }
246
247 private static class ElementWrapper {
248 private ASTMethodDeclaration node;
249
250 public ElementWrapper(ASTMethodDeclaration node) {
251 this.node = node;
252 }
253
254 public ASTMethodDeclaration getNode() {
255 return node;
256 }
257
258 @Override
259 public String toString() {
260 return node.getMethodName();
261 }
262 }
263
264 private DFACanvas dfaCanvas;
265 private JList nodeList;
266 private DefaultListModel nodes = new DefaultListModel();
267
268 public DFAPanel() {
269 super();
270
271 setLayout(new BorderLayout());
272 JPanel leftPanel = new JPanel();
273
274 nodeList = new JList(nodes);
275 nodeList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
276 nodeList.setFixedCellWidth(150);
277 nodeList.setBorder(BorderFactory.createLineBorder(Color.black));
278 nodeList.addListSelectionListener(this);
279
280 leftPanel.add(nodeList);
281 add(leftPanel, BorderLayout.WEST);
282
283 dfaCanvas = new DFACanvas();
284 dfaCanvas.setBackground(Color.WHITE);
285 dfaCanvas.setPreferredSize(new Dimension(900, 1400));
286
287 JScrollPane scrollPane = new JScrollPane(dfaCanvas);
288
289 add(scrollPane, BorderLayout.CENTER);
290 }
291
292 public void valueChanged(ListSelectionEvent event) {
293 ElementWrapper wrapper = null;
294 if (nodes.size() == 1) {
295 wrapper = (ElementWrapper) nodes.get(0);
296 } else if (nodes.isEmpty()) {
297 return;
298 } else if (nodeList.getSelectedValue() == null) {
299 wrapper = (ElementWrapper) nodes.get(0);
300 } else {
301 wrapper = (ElementWrapper) nodeList.getSelectedValue();
302 }
303 dfaCanvas.setMethod(wrapper.getNode());
304 dfaCanvas.repaint();
305 }
306
307 public void resetTo(List<ASTMethodDeclaration> newNodes, LineGetter lines) {
308 dfaCanvas.setCode(lines);
309 nodes.clear();
310 for (ASTMethodDeclaration md : newNodes) {
311 nodes.addElement(new ElementWrapper(md));
312 }
313 nodeList.setSelectedIndex(0);
314 dfaCanvas.setMethod(newNodes.get(0));
315 repaint();
316 }
317 }
|