DFAPanel.java
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 = * 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, 0360);
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, idx2;
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 - * 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 * 3;
176       final int width = NODE_RADIUS * 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 - 210 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 - 210 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(9001400));
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 = (ElementWrappernodes.get(0);
296   else if (nodes.isEmpty()) {
297       return;
298   else if (nodeList.getSelectedValue() == null) {
299       wrapper = (ElementWrappernodes.get(0);
300   else {
301       wrapper = (ElementWrappernodeList.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 }