1 /***
2 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3 */
4 package net.sourceforge.pmd.util.designer;
5
6 import net.sourceforge.pmd.PMD;
7 import net.sourceforge.pmd.RuleContext;
8 import net.sourceforge.pmd.RuleSet;
9 import net.sourceforge.pmd.SourceType;
10 import net.sourceforge.pmd.TargetJDK1_3;
11 import net.sourceforge.pmd.TargetJDK1_4;
12 import net.sourceforge.pmd.TargetJDK1_5;
13 import net.sourceforge.pmd.TargetJDK1_6;
14 import net.sourceforge.pmd.ast.Node;
15 import net.sourceforge.pmd.ast.ParseException;
16 import net.sourceforge.pmd.ast.SimpleNode;
17 import net.sourceforge.pmd.jaxen.DocumentNavigator;
18 import net.sourceforge.pmd.jaxen.MatchesFunction;
19 import net.sourceforge.pmd.jsp.ast.JspCharStream;
20 import net.sourceforge.pmd.jsp.ast.JspParser;
21 import net.sourceforge.pmd.util.NumericConstants;
22 import net.sourceforge.pmd.util.StringUtil;
23 import org.jaxen.BaseXPath;
24 import org.jaxen.JaxenException;
25 import org.jaxen.XPath;
26
27 import javax.swing.*;
28 import javax.swing.event.*;
29 import javax.swing.text.JTextComponent;
30 import javax.swing.tree.DefaultTreeCellRenderer;
31 import javax.swing.tree.DefaultTreeModel;
32 import javax.swing.tree.TreeNode;
33 import javax.swing.tree.TreePath;
34 import javax.swing.undo.*;
35 import java.awt.BorderLayout;
36 import java.awt.Color;
37 import java.awt.Component;
38 import java.awt.Dimension;
39 import java.awt.Font;
40 import java.awt.FontMetrics;
41 import java.awt.Graphics;
42 import java.awt.Toolkit;
43 import java.awt.datatransfer.Clipboard;
44 import java.awt.datatransfer.ClipboardOwner;
45 import java.awt.datatransfer.StringSelection;
46 import java.awt.datatransfer.Transferable;
47 import java.awt.event.ActionEvent;
48 import java.awt.event.ActionListener;
49 import java.awt.event.ComponentEvent;
50 import java.awt.event.KeyEvent;
51 import java.io.IOException;
52 import java.io.StringReader;
53 import java.io.StringWriter;
54 import java.lang.reflect.InvocationTargetException;
55 import java.lang.reflect.Method;
56 import java.util.Enumeration;
57 import java.util.Iterator;
58 import java.util.List;
59 import java.util.Vector;
60
61 public class Designer implements ClipboardOwner {
62
63 private static final char LABEL_IMAGE_SEPARATOR = ':';
64 private static final Color IMAGE_TEXT_COLOR = Color.BLUE;
65
66 private interface Parser { public SimpleNode parse(StringReader sr); };
67
68 private static final Parser jdkParser1_3 = new Parser() {
69 public SimpleNode parse(StringReader sr) { return new TargetJDK1_3().createParser(sr).CompilationUnit(); };
70 };
71
72 private static final Parser jdkParser1_4 = new Parser() {
73 public SimpleNode parse(StringReader sr) { return new TargetJDK1_4().createParser(sr).CompilationUnit(); };
74 };
75
76 private static final Parser jdkParser1_5 = new Parser() {
77 public SimpleNode parse(StringReader sr) { return new TargetJDK1_5().createParser(sr).CompilationUnit(); };
78 };
79
80 private static final Parser jdkParser1_6 = new Parser() {
81 public SimpleNode parse(StringReader sr) { return new TargetJDK1_6().createParser(sr).CompilationUnit(); };
82 };
83
84 private static final Parser jspParser = new Parser() {
85 public SimpleNode parse(StringReader sr) { return new JspParser(new JspCharStream(sr)).CompilationUnit(); };
86 };
87
88 private static final Object[][] sourceTypeSets = new Object[][] {
89 { "JDK 1.3", SourceType.JAVA_13, jdkParser1_3 },
90 { "JDK 1.4", SourceType.JAVA_14, jdkParser1_4 },
91 { "JDK 1.5", SourceType.JAVA_15, jdkParser1_5 },
92 { "JDK 1.6", SourceType.JAVA_16, jdkParser1_6 },
93 { "JSP", SourceType.JSP, jspParser }
94 };
95
96 private static final int defaultSourceTypeSelectionIndex = 1;
97
98
99 private SimpleNode getCompilationUnit() {
100
101 Parser parser = (Parser)sourceTypeSets[selectedSourceTypeIndex()][2];
102 return parser.parse(new StringReader(codeEditorPane.getText()));
103 }
104
105 private SourceType getSourceType() {
106
107 return (SourceType)sourceTypeSets[selectedSourceTypeIndex()][1];
108 }
109
110 private int selectedSourceTypeIndex() {
111 for (int i=0; i<sourceTypeMenuItems.length; i++) {
112 if (sourceTypeMenuItems[i].isSelected()) return i;
113 }
114 throw new RuntimeException("Initial default source type not specified");
115 }
116
117 private class ExceptionNode implements TreeNode {
118
119 private Object item;
120 private ExceptionNode[] kids;
121
122 public ExceptionNode(Object theItem) {
123 item = theItem;
124
125 if (item instanceof ParseException) createKids();
126 }
127
128
129 private void createKids() {
130
131 String message = ((ParseException)item).getMessage();
132 String[] lines = StringUtil.substringsOf(message, PMD.EOL);
133
134 kids = new ExceptionNode[lines.length];
135 for (int i=0; i<lines.length; i++) {
136 kids[i] = new ExceptionNode(lines[i]);
137 }
138 }
139
140 public int getChildCount() { return kids == null ? 0 : kids.length; }
141 public boolean getAllowsChildren() {return false; }
142 public boolean isLeaf() { return kids == null; }
143 public TreeNode getParent() { return null; }
144 public TreeNode getChildAt(int childIndex) { return kids[childIndex]; }
145 public String label() { return item.toString(); }
146
147 public Enumeration children() {
148 Enumeration e = new Enumeration() {
149 int i = 0;
150 public boolean hasMoreElements() {
151 return kids != null && i < kids.length;
152 }
153
154 public Object nextElement() { return kids[i++]; }
155 };
156 return e;
157 }
158
159 public int getIndex(TreeNode node) {
160 for (int i=0; i<kids.length; i++) {
161 if (kids[i] == node) return i;
162 }
163 return -1;
164 }
165 }
166
167
168
169 private class ASTTreeNode implements TreeNode {
170
171 private Node node;
172 private ASTTreeNode parent;
173 private ASTTreeNode[] kids;
174
175 public ASTTreeNode(Node theNode) {
176 node = theNode;
177
178 Node prnt = node.jjtGetParent();
179 if (prnt != null) parent = new ASTTreeNode(prnt);
180 }
181
182 public int getChildCount() { return node.jjtGetNumChildren(); }
183 public boolean getAllowsChildren() { return false; }
184 public boolean isLeaf() { return node.jjtGetNumChildren() == 0; }
185 public TreeNode getParent() { return parent; }
186
187 public Enumeration children() {
188
189 if (getChildCount() > 0) getChildAt(0);
190
191 Enumeration e = new Enumeration() {
192 int i = 0;
193 public boolean hasMoreElements() {
194 return kids != null && i < kids.length;
195 }
196 public Object nextElement() { return kids[i++]; }
197 };
198 return e;
199 }
200
201 public TreeNode getChildAt(int childIndex) {
202
203 if (kids == null) {
204 kids = new ASTTreeNode[node.jjtGetNumChildren()];
205 for (int i=0; i<kids.length; i++) {
206 kids[i] = new ASTTreeNode(node.jjtGetChild(i));
207 }
208 }
209 return kids[childIndex];
210 }
211
212 public int getIndex(TreeNode node) {
213
214 for (int i=0; i<kids.length; i++) {
215 if (kids[i] == node) return i;
216 }
217 return -1;
218 }
219
220 public String label() {
221 if (node instanceof SimpleNode) {
222 SimpleNode sn = (SimpleNode)node;
223 if (sn.getLabel() != null) {
224 return node.toString() + LABEL_IMAGE_SEPARATOR + sn.getLabel();
225 }
226 if (sn.getImage() == null) {
227 return node.toString();
228 }
229 return node.toString() + LABEL_IMAGE_SEPARATOR + sn.getImage();
230 }
231 return node.toString();
232 }
233 }
234
235
236
237
238
239 private class ASTCellRenderer extends DefaultTreeCellRenderer {
240
241 private ASTTreeNode node;
242
243 public Icon getIcon() { return null; };
244
245 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel,boolean expanded,boolean leaf, int row, boolean hasFocus) {
246
247 if (value instanceof ASTTreeNode) {
248 node = (ASTTreeNode)value;
249 }
250 return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
251 }
252
253
254 public void paint(Graphics g) {
255
256 super.paint(g);
257
258 if (node == null) return;
259
260 String text = node.label();
261 int separatorPos = text.indexOf(LABEL_IMAGE_SEPARATOR);
262 if (separatorPos < 0) return;
263
264 String label = text.substring(0, separatorPos+1);
265 String image = text.substring(separatorPos+1);
266
267 FontMetrics fm = g.getFontMetrics();
268 int width = SwingUtilities.computeStringWidth(fm, label);
269
270 g.setColor(IMAGE_TEXT_COLOR);
271 g.drawString(image, width, fm.getMaxAscent());
272 }
273 }
274
275
276
277
278 private class ASTTreeWidget extends JTree {
279
280 public ASTTreeWidget(Vector items) {
281 super(items);
282 }
283
284 public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
285 if (value == null) return "";
286 if (value instanceof ASTTreeNode) {
287 return ((ASTTreeNode)value).label();
288 }
289 if (value instanceof ExceptionNode) {
290 return ((ExceptionNode)value).label();
291 }
292 return value.toString();
293 }
294
295 public void expandAll(boolean expand) {
296 TreeNode root = (TreeNode)getModel().getRoot();
297 expandAll(new TreePath(root), expand);
298 }
299
300 private void expandAll(TreePath parent, boolean expand) {
301
302 TreeNode node = (TreeNode)parent.getLastPathComponent();
303 if (node.getChildCount() >= 0) {
304 for (Enumeration e=node.children(); e.hasMoreElements(); ) {
305 TreeNode n = (TreeNode)e.nextElement();
306 TreePath path = parent.pathByAddingChild(n);
307 expandAll(path, expand);
308 }
309 }
310
311 if (expand) {
312 expandPath(parent);
313 } else {
314 collapsePath(parent);
315 }
316 }
317 }
318
319 private void loadTreeData(TreeNode rootNode) {
320 astWidget.setModel(new DefaultTreeModel(rootNode));
321 astWidget.expandAll(true);
322 }
323
324 private class ShowListener implements ActionListener {
325 public void actionPerformed(ActionEvent ae) {
326 MyPrintStream ps = new MyPrintStream();
327 System.setOut(ps);
328 TreeNode tn;
329 try {
330 SimpleNode lastCompilationUnit = getCompilationUnit();
331 tn = new ASTTreeNode(lastCompilationUnit);
332 } catch (ParseException pe) {
333 tn = new ExceptionNode(pe);
334 }
335
336 loadTreeData(tn);
337 }
338 }
339
340 private class DFAListener implements ActionListener {
341 public void actionPerformed(ActionEvent ae) {
342
343 DFAGraphRule dfaGraphRule = new DFAGraphRule();
344 RuleSet rs = new RuleSet();
345 SourceType sourceType = getSourceType();
346 if (!sourceType.equals(SourceType.JSP)){
347 rs.addRule(dfaGraphRule);
348 }
349 RuleContext ctx = new RuleContext();
350 ctx.setSourceCodeFilename("[no filename]");
351 StringReader reader = new StringReader(codeEditorPane.getText());
352 PMD pmd = new PMD();
353 pmd.setJavaVersion(sourceType);
354
355 try {
356 pmd.processFile(reader, rs, ctx);
357
358
359 } catch (Exception e) {
360 e.printStackTrace();
361 }
362
363 List methods = dfaGraphRule.getMethods();
364 if (methods != null && !methods.isEmpty()) {
365 dfaPanel.resetTo(methods, codeEditorPane);
366 dfaPanel.repaint();
367 }
368 }
369 }
370
371 private class XPathListener implements ActionListener {
372 public void actionPerformed(ActionEvent ae) {
373 xpathResults.clear();
374 if (xpathQueryArea.getText().length() == 0) {
375 xpathResults.addElement("XPath query field is empty");
376 xpathResultList.repaint();
377 codeEditorPane.requestFocus();
378 return;
379 }
380 SimpleNode c = getCompilationUnit();
381 try {
382 XPath xpath = new BaseXPath(xpathQueryArea.getText(), new DocumentNavigator());
383 for (Iterator iter = xpath.selectNodes(c).iterator(); iter.hasNext();) {
384 StringBuffer sb = new StringBuffer();
385 Object obj = iter.next();
386 if (obj instanceof String) {
387 System.out.println("Result was a string: " + ((String) obj));
388 } else if (!(obj instanceof Boolean)) {
389
390 SimpleNode node = (SimpleNode) obj;
391 String name = node.getClass().getName().substring(node.getClass().getName().lastIndexOf('.') + 1);
392 String line = " at line " + node.getBeginLine();
393 sb.append(name).append(line).append(PMD.EOL);
394 xpathResults.addElement(sb.toString().trim());
395 }
396 }
397 if (xpathResults.isEmpty()) {
398 xpathResults.addElement("No matching nodes " + System.currentTimeMillis());
399 }
400 } catch (ParseException pe) {
401 xpathResults.addElement(pe.fillInStackTrace().getMessage());
402 } catch (JaxenException je) {
403 xpathResults.addElement(je.fillInStackTrace().getMessage());
404 }
405 xpathResultList.repaint();
406 xpathQueryArea.requestFocus();
407 }
408 }
409
410 private final CodeEditorTextPane codeEditorPane = new CodeEditorTextPane();
411 private final ASTTreeWidget astWidget = new ASTTreeWidget(new Vector());
412 private DefaultListModel xpathResults = new DefaultListModel();
413 private final JList xpathResultList = new JList(xpathResults);
414 private final JTextArea xpathQueryArea = new JTextArea(15, 30);
415 private final JFrame frame = new JFrame("PMD Rule Designer");
416 private final DFAPanel dfaPanel = new DFAPanel();
417 private final JRadioButtonMenuItem[] sourceTypeMenuItems = new JRadioButtonMenuItem[sourceTypeSets.length];
418
419 public Designer() {
420 MatchesFunction.registerSelfInSimpleContext();
421
422 xpathQueryArea.setFont(new Font("Verdana", Font.PLAIN, 16));
423 makeTextComponentUndoable(codeEditorPane);
424 JSplitPane controlSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(codeEditorPane), createXPathQueryPanel());
425 JSplitPane resultsSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, createASTPanel(), createXPathResultPanel());
426
427 JTabbedPane tabbed = new JTabbedPane();
428 tabbed.addTab("Abstract Syntax Tree / XPath", resultsSplitPane);
429 tabbed.addTab("Data Flow Analysis", dfaPanel);
430 try {
431
432 Method setMnemonicAt = JTabbedPane.class.getMethod("setMnemonicAt", new Class[]{Integer.TYPE, Integer.TYPE});
433 if (setMnemonicAt != null) {
434
435
436
437 setMnemonicAt.invoke(tabbed, new Object[]{NumericConstants.ZERO, new Integer(KeyEvent.VK_A)});
438 setMnemonicAt.invoke(tabbed, new Object[]{NumericConstants.ONE, new Integer(KeyEvent.VK_D)});
439 }
440 } catch (NoSuchMethodException nsme) {
441 } catch (IllegalAccessException e) {
442 e.printStackTrace();
443 throw new InternalError("Runtime reports to be >= JDK 1.4 yet String.split(java.lang.String) is broken.");
444 } catch (IllegalArgumentException e) {
445 e.printStackTrace();
446 throw new InternalError("Runtime reports to be >= JDK 1.4 yet String.split(java.lang.String) is broken.");
447 } catch (InvocationTargetException e) {
448 e.printStackTrace();
449 throw new InternalError("Runtime reports to be >= JDK 1.4 yet String.split(java.lang.String) is broken.");
450 }
451
452 JSplitPane containerSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, controlSplitPane, tabbed);
453 containerSplitPane.setContinuousLayout(true);
454
455 JMenuBar menuBar = createMenuBar();
456 frame.setJMenuBar(menuBar);
457 frame.getContentPane().add(containerSplitPane);
458 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
459
460 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
461 int screenHeight = screenSize.height;
462 int screenWidth = screenSize.width;
463
464 frame.pack();
465 frame.setSize((screenWidth*3/4),(screenHeight*3/4));
466 frame.setLocation((screenWidth -frame.getWidth()) / 2, (screenHeight - frame.getHeight()) / 2);
467 frame.setVisible(true);
468 resultsSplitPane.setDividerLocation(resultsSplitPane.getMaximumDividerLocation() - (resultsSplitPane.getMaximumDividerLocation() / 2));
469 containerSplitPane.setDividerLocation(containerSplitPane.getMaximumDividerLocation() / 2);
470 }
471
472 private JMenuBar createMenuBar() {
473 JMenuBar menuBar = new JMenuBar();
474 JMenu menu = new JMenu("JDK");
475 ButtonGroup group = new ButtonGroup();
476
477 for (int i=0; i<sourceTypeSets.length; i++) {
478 JRadioButtonMenuItem button = new JRadioButtonMenuItem(sourceTypeSets[i][0].toString());
479 sourceTypeMenuItems[i] = button;
480 group.add(button);
481 menu.add(button);
482 }
483 sourceTypeMenuItems[defaultSourceTypeSelectionIndex].setSelected(true);
484 menuBar.add(menu);
485
486 JMenu actionsMenu = new JMenu("Actions");
487 JMenuItem copyXMLItem = new JMenuItem("Copy xml to clipboard");
488 copyXMLItem.addActionListener(new ActionListener() {
489 public void actionPerformed(ActionEvent e) {
490 copyXmlToClipboard();
491 }
492 });
493 actionsMenu.add(copyXMLItem);
494 JMenuItem createRuleXMLItem = new JMenuItem("Create rule XML");
495 createRuleXMLItem.addActionListener(new ActionListener() {
496 public void actionPerformed(ActionEvent e) {
497 createRuleXML();
498 }
499 });
500 actionsMenu.add(createRuleXMLItem);
501 menuBar.add(actionsMenu);
502
503 return menuBar;
504 }
505
506 private void createRuleXML() {
507 CreateXMLRulePanel rulePanel = new CreateXMLRulePanel(xpathQueryArea, codeEditorPane);
508 JFrame xmlframe = new JFrame("Create XML Rule");
509 xmlframe.setContentPane(rulePanel);
510 xmlframe.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
511 xmlframe.setSize(new Dimension(600, 700));
512 xmlframe.addComponentListener(new java.awt.event.ComponentAdapter() {
513 public void componentResized(ComponentEvent e) {
514 JFrame tmp = (JFrame)e.getSource();
515 if (tmp.getWidth()<600 || tmp.getHeight()<700) {
516 tmp.setSize(600, 700);
517 }
518 }
519 });
520 int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
521 int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;
522 xmlframe.pack();
523 xmlframe.setLocation((screenWidth - xmlframe.getWidth()) / 2, (screenHeight - xmlframe.getHeight()) / 2);
524 xmlframe.setVisible(true);
525 }
526
527 private JComponent createASTPanel() {
528 astWidget.setCellRenderer(new ASTCellRenderer());
529 return new JScrollPane(astWidget);
530 }
531
532 private JComponent createXPathResultPanel() {
533 xpathResults.addElement("No results yet");
534 xpathResultList.setBorder(BorderFactory.createLineBorder(Color.black));
535 xpathResultList.setFixedCellWidth(300);
536 JScrollPane scrollPane = new JScrollPane();
537 scrollPane.getViewport().setView(xpathResultList);
538 return scrollPane;
539 }
540
541 private JPanel createXPathQueryPanel() {
542 JPanel p = new JPanel();
543 p.setLayout(new BorderLayout());
544 xpathQueryArea.setBorder(BorderFactory.createLineBorder(Color.black));
545 makeTextComponentUndoable(xpathQueryArea);
546 JScrollPane scrollPane = new JScrollPane(xpathQueryArea);
547 scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
548 scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
549 final JButton b = createGoButton();
550
551 p.add(new JLabel("XPath Query (if any)"), BorderLayout.NORTH);
552 p.add(scrollPane, BorderLayout.CENTER);
553 p.add(b, BorderLayout.SOUTH);
554
555 return p;
556 }
557
558 private JButton createGoButton() {
559 JButton b = new JButton("Go");
560 b.setMnemonic('g');
561 b.addActionListener(new ShowListener());
562 b.addActionListener(codeEditorPane);
563 b.addActionListener(new XPathListener());
564 b.addActionListener(new DFAListener());
565 return b;
566 }
567
568 private static void makeTextComponentUndoable(JTextComponent textConponent) {
569 final UndoManager undoManager = new UndoManager();
570 textConponent.getDocument().addUndoableEditListener(new UndoableEditListener() {
571 public void undoableEditHappened(
572 UndoableEditEvent evt) {
573 undoManager.addEdit(evt.getEdit());
574 }
575 });
576 ActionMap actionMap = textConponent.getActionMap();
577 InputMap inputMap = textConponent.getInputMap();
578 actionMap.put("Undo", new AbstractAction("Undo") {
579 public void actionPerformed(ActionEvent evt) {
580 try {
581 if (undoManager.canUndo()) {
582 undoManager.undo();
583 }
584 } catch (CannotUndoException e) {
585 }
586 }
587 });
588 inputMap.put(KeyStroke.getKeyStroke("control Z"), "Undo");
589
590 actionMap.put("Redo", new AbstractAction("Redo") {
591 public void actionPerformed(ActionEvent evt) {
592 try {
593 if (undoManager.canRedo()) {
594 undoManager.redo();
595 }
596 } catch (CannotRedoException e) {
597 }
598 }
599 });
600 inputMap.put(KeyStroke.getKeyStroke("control Y"), "Redo");
601 }
602
603 public static void main(String[] args) {
604 new Designer();
605 }
606
607 private final void copyXmlToClipboard() {
608 if (codeEditorPane.getText() != null && codeEditorPane.getText().trim().length() > 0) {
609 String xml = "";
610 SimpleNode cu = getCompilationUnit();
611 if (cu != null) {
612 try {
613 xml = getXmlString(cu);
614 } catch (IOException e) {
615 e.printStackTrace();
616 xml = "Error trying to construct XML representation";
617 }
618 }
619 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(xml), this);
620 }
621 }
622
623 /***
624 * Returns an unformatted xml string (without the declaration)
625 *
626 * @param node
627 * @return String
628 * @throws java.io.IOException
629 */
630 private String getXmlString(SimpleNode node) throws IOException {
631
632
633
634
635
636
637
638 return "FIXME";
639 }
640
641 public void lostOwnership(Clipboard clipboard, Transferable contents) {
642 }
643 }
644