View Javadoc

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 java.awt.BorderLayout;
7   import java.awt.Color;
8   import java.awt.Component;
9   import java.awt.Dimension;
10  import java.awt.Font;
11  import java.awt.Toolkit;
12  import java.awt.datatransfer.Clipboard;
13  import java.awt.datatransfer.ClipboardOwner;
14  import java.awt.datatransfer.StringSelection;
15  import java.awt.datatransfer.Transferable;
16  import java.awt.event.ActionEvent;
17  import java.awt.event.ActionListener;
18  import java.awt.event.ComponentEvent;
19  import java.awt.event.KeyEvent;
20  import java.awt.event.MouseEvent;
21  import java.io.StringReader;
22  import java.io.StringWriter;
23  import java.lang.reflect.InvocationTargetException;
24  import java.lang.reflect.Method;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.Enumeration;
28  import java.util.Iterator;
29  import java.util.List;
30  
31  import javax.swing.AbstractAction;
32  import javax.swing.ActionMap;
33  import javax.swing.BorderFactory;
34  import javax.swing.ButtonGroup;
35  import javax.swing.DefaultListModel;
36  import javax.swing.InputMap;
37  import javax.swing.JButton;
38  import javax.swing.JComponent;
39  import javax.swing.JFrame;
40  import javax.swing.JLabel;
41  import javax.swing.JList;
42  import javax.swing.JMenu;
43  import javax.swing.JMenuBar;
44  import javax.swing.JMenuItem;
45  import javax.swing.JPanel;
46  import javax.swing.JRadioButtonMenuItem;
47  import javax.swing.JScrollPane;
48  import javax.swing.JSplitPane;
49  import javax.swing.JTabbedPane;
50  import javax.swing.JTextArea;
51  import javax.swing.JTree;
52  import javax.swing.KeyStroke;
53  import javax.swing.ListCellRenderer;
54  import javax.swing.ListSelectionModel;
55  import javax.swing.ScrollPaneConstants;
56  import javax.swing.WindowConstants;
57  import javax.swing.event.ListSelectionEvent;
58  import javax.swing.event.ListSelectionListener;
59  import javax.swing.event.TreeSelectionEvent;
60  import javax.swing.event.TreeSelectionListener;
61  import javax.swing.event.UndoableEditEvent;
62  import javax.swing.event.UndoableEditListener;
63  import javax.swing.text.JTextComponent;
64  import javax.swing.tree.DefaultMutableTreeNode;
65  import javax.swing.tree.DefaultTreeCellRenderer;
66  import javax.swing.tree.DefaultTreeModel;
67  import javax.swing.tree.TreeCellRenderer;
68  import javax.swing.tree.TreeNode;
69  import javax.swing.tree.TreePath;
70  import javax.swing.tree.TreeSelectionModel;
71  import javax.swing.undo.CannotRedoException;
72  import javax.swing.undo.CannotUndoException;
73  import javax.swing.undo.UndoManager;
74  import javax.xml.transform.OutputKeys;
75  import javax.xml.transform.Result;
76  import javax.xml.transform.Source;
77  import javax.xml.transform.Transformer;
78  import javax.xml.transform.TransformerException;
79  import javax.xml.transform.TransformerFactory;
80  import javax.xml.transform.dom.DOMSource;
81  import javax.xml.transform.stream.StreamResult;
82  
83  import net.sourceforge.pmd.PMD;
84  import net.sourceforge.pmd.RuleContext;
85  import net.sourceforge.pmd.RuleSet;
86  import net.sourceforge.pmd.SourceType;
87  import net.sourceforge.pmd.ast.ASTMethodDeclaration;
88  import net.sourceforge.pmd.ast.AccessNode;
89  import net.sourceforge.pmd.ast.Node;
90  import net.sourceforge.pmd.ast.ParseException;
91  import net.sourceforge.pmd.ast.SimpleNode;
92  import net.sourceforge.pmd.jaxen.DocumentNavigator;
93  import net.sourceforge.pmd.jaxen.MatchesFunction;
94  import net.sourceforge.pmd.jaxen.TypeOfFunction;
95  import net.sourceforge.pmd.parsers.Parser;
96  import net.sourceforge.pmd.sourcetypehandlers.SourceTypeHandler;
97  import net.sourceforge.pmd.sourcetypehandlers.SourceTypeHandlerBroker;
98  import net.sourceforge.pmd.symboltable.ClassNameDeclaration;
99  import net.sourceforge.pmd.symboltable.ClassScope;
100 import net.sourceforge.pmd.symboltable.LocalScope;
101 import net.sourceforge.pmd.symboltable.MethodNameDeclaration;
102 import net.sourceforge.pmd.symboltable.MethodScope;
103 import net.sourceforge.pmd.symboltable.NameOccurrence;
104 import net.sourceforge.pmd.symboltable.Scope;
105 import net.sourceforge.pmd.symboltable.SourceFileScope;
106 import net.sourceforge.pmd.symboltable.VariableNameDeclaration;
107 import net.sourceforge.pmd.util.NumericConstants;
108 import net.sourceforge.pmd.util.StringUtil;
109 
110 import org.jaxen.BaseXPath;
111 import org.jaxen.JaxenException;
112 import org.jaxen.XPath;
113 
114 public class Designer implements ClipboardOwner {
115 
116 	private static final char LABEL_IMAGE_SEPARATOR = ':';
117 
118 	private static final Object[][] sourceTypeSets = new Object[][] {
119 		{ "JDK 1.3", SourceType.JAVA_13 },
120 		{ "JDK 1.4", SourceType.JAVA_14 },
121 		{ "JDK 1.5", SourceType.JAVA_15 },
122 		{ "JDK 1.6", SourceType.JAVA_16 },
123 		{ "JDK 1.7", SourceType.JAVA_17 },
124 		{ "JSP", 	 SourceType.JSP }
125 		};
126 
127 	private static final int defaultSourceTypeSelectionIndex = 2; // Java 1.5
128 
129     private SimpleNode getCompilationUnit() {
130     	SourceTypeHandler handler = SourceTypeHandlerBroker.getVisitorsFactoryForSourceType(getSourceType());
131     	Parser parser = handler.getParser();
132     	parser.setExcludeMarker(PMD.EXCLUDE_MARKER);
133     	SimpleNode simpleNode = (SimpleNode)parser.parse(new StringReader(codeEditorPane.getText()));
134     	handler.getSymbolFacade().start(simpleNode);
135     	handler.getTypeResolutionFacade(null).start(simpleNode);
136     	return simpleNode;
137     }
138 
139     private SourceType getSourceType() {
140 
141     	return (SourceType)sourceTypeSets[selectedSourceTypeIndex()][1];
142     }
143 
144     private int selectedSourceTypeIndex() {
145     	for (int i=0; i<sourceTypeMenuItems.length; i++) {
146     		if (sourceTypeMenuItems[i].isSelected()) return i;
147     	}
148     	throw new RuntimeException("Initial default source type not specified");
149     }
150 
151     private class ExceptionNode implements TreeNode {
152 
153     	private Object 			item;
154     	private ExceptionNode[] kids;
155 
156     	public ExceptionNode(Object theItem) {
157     		item = theItem;
158 
159     		if (item instanceof ParseException) createKids();
160     	}
161 
162     	// each line in the error message becomes a separate tree node
163     	private void createKids() {
164 
165     		String message = ((ParseException)item).getMessage();
166             String[] lines = StringUtil.substringsOf(message, PMD.EOL);
167 
168 			kids = new ExceptionNode[lines.length];
169 			for (int i=0; i<lines.length; i++) {
170 				kids[i] = new ExceptionNode(lines[i]);
171 			}
172     	}
173 
174 		public int getChildCount() { return kids == null ? 0 : kids.length; }
175 		public boolean getAllowsChildren() {return false; }
176 		public boolean isLeaf() { return kids == null; }
177 		public TreeNode getParent() { return null; }
178 		public TreeNode getChildAt(int childIndex) { return kids[childIndex]; }
179 		public String label() {	return item.toString();	}
180 
181 		public Enumeration children() {
182 			Enumeration e = new Enumeration() {
183 				int i = 0;
184 				public boolean hasMoreElements() {
185 					return kids != null && i < kids.length;
186 				}
187 
188 				public Object nextElement() { return kids[i++]; }
189 				};
190 			return e;
191 		}
192 
193 		public int getIndex(TreeNode node) {
194 			for (int i=0; i<kids.length; i++) {
195 				if (kids[i] == node) return i;
196 			}
197 			return -1;
198 		}
199     }
200 
201     // Tree node that wraps the AST node for the tree widget and
202     // any possible children they may have.
203     private class ASTTreeNode implements TreeNode {
204 
205     	private Node 			node;
206     	private ASTTreeNode 	parent;
207     	private ASTTreeNode[] 	kids;
208 
209     	public ASTTreeNode(Node theNode) {
210     		node = theNode;
211 
212     		Node prnt = node.jjtGetParent();
213     		if (prnt != null) parent = new ASTTreeNode(prnt);
214     	}
215 
216 		public int getChildCount() { return node.jjtGetNumChildren(); }
217 		public boolean getAllowsChildren() { return false;	}
218 		public boolean isLeaf() { return node.jjtGetNumChildren() == 0;	}
219 		public TreeNode getParent() { return parent; }
220 
221 		public Scope getScope() {
222     		if (node instanceof SimpleNode)
223     			return ((SimpleNode)node).getScope();
224     		return null;
225     	}
226 
227 		public Enumeration children() {
228 
229 			if (getChildCount() > 0) getChildAt(0);	// force it to build kids
230 
231 			Enumeration e = new Enumeration() {
232 				int i = 0;
233 				public boolean hasMoreElements() {
234 					return kids != null && i < kids.length;
235 				}
236 				public Object nextElement() { return kids[i++]; }
237 				};
238 			return e;
239 		}
240 
241 		public TreeNode getChildAt(int childIndex) {
242 
243 			if (kids == null) {
244 				kids = new ASTTreeNode[node.jjtGetNumChildren()];
245     			for (int i=0; i<kids.length; i++) {
246     				kids[i] = new ASTTreeNode(node.jjtGetChild(i));
247     				}
248 				}
249 			return kids[childIndex];
250 		}
251 
252 		public int getIndex(TreeNode node) {
253 
254 			for (int i=0; i<kids.length; i++) {
255 				if (kids[i] == node) return i;
256 			}
257 			return -1;
258 		}
259 
260 		public String label() {
261 			if (node instanceof SimpleNode) {
262 				SimpleNode sn = (SimpleNode)node;
263                 if (sn.getLabel() != null) {
264                     return node.toString() + LABEL_IMAGE_SEPARATOR + sn.getLabel();
265                 }
266 				if (sn.getImage() == null) {
267                     return node.toString();
268                 }
269 				return node.toString() + LABEL_IMAGE_SEPARATOR + sn.getImage();
270 			}
271 			return node.toString();
272 		}
273 
274 		public String getToolTipText() {
275 			String tooltip = "";
276 		    if (node instanceof SimpleNode) {
277 		        SimpleNode sn = (SimpleNode)node;
278 		        tooltip = "Line: " + sn.getBeginLine() + " Column: " + sn.getBeginColumn();
279 		    }
280 
281 		    if (node instanceof AccessNode)
282 		    {
283 		    	AccessNode accessNode = (AccessNode)node;
284 		    	if ( ! "".equals(tooltip))
285 		    		tooltip += ",";
286 		    	tooltip += accessNode.isAbstract() ? " Abstract" : "";
287 		    	tooltip += accessNode.isStatic() ? " Static" : "";
288 		    	tooltip += accessNode.isFinal() ? " Final" : "";
289 		    	tooltip += accessNode.isNative() ? " Native" : "";
290 		    	tooltip += accessNode.isPrivate() ? " Private" : "";
291 		    	tooltip += accessNode.isSynchronized() ? " Synchronised" : "";
292 		    	tooltip += accessNode.isTransient() ? " Transient" : "";
293 		    	tooltip += accessNode.isVolatile() ? " Volatile" : "";
294 		    	tooltip += accessNode.isStrictfp() ? " Strictfp" : "";
295 		    }
296 		    return tooltip;
297 		}
298     }
299 
300     private TreeCellRenderer createNoImageTreeCellRenderer() {
301     	DefaultTreeCellRenderer treeCellRenderer = new DefaultTreeCellRenderer();
302     	treeCellRenderer.setLeafIcon(null);
303     	treeCellRenderer.setOpenIcon(null);
304     	treeCellRenderer.setClosedIcon(null);
305     	return treeCellRenderer;
306     }
307 
308     // Special tree variant that knows how to retrieve node labels and
309     // provides the ability to expand all nodes at once.
310     private class TreeWidget extends JTree {
311 
312 		private static final long serialVersionUID = 1L;
313 
314 		public TreeWidget(Object[] items) {
315     		super(items);
316             setToolTipText("");
317     	}
318 
319         public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
320         	if (value == null) return "";
321         	if (value instanceof ASTTreeNode) {
322         		return ((ASTTreeNode)value).label();
323         	}
324         	if (value instanceof ExceptionNode) {
325         		return ((ExceptionNode)value).label();
326         	}
327         	return value.toString();
328     	}
329 
330         public String getToolTipText(MouseEvent e) {
331             if (getRowForLocation(e.getX(), e.getY()) == -1) return null;
332             TreePath curPath = getPathForLocation(e.getX(), e.getY());
333             if (curPath.getLastPathComponent() instanceof ASTTreeNode) {
334             	return ((ASTTreeNode)curPath.getLastPathComponent()).getToolTipText();
335             } else {
336             	return super.getToolTipText(e);
337             }
338         }
339 
340         public void expandAll(boolean expand) {
341             TreeNode root = (TreeNode)getModel().getRoot();
342             expandAll(new TreePath(root), expand);
343         }
344 
345         private void expandAll(TreePath parent, boolean expand) {
346             // Traverse children
347             TreeNode node = (TreeNode)parent.getLastPathComponent();
348             if (node.getChildCount() >= 0) {
349                 for (Enumeration e=node.children(); e.hasMoreElements(); ) {
350                     TreeNode n = (TreeNode)e.nextElement();
351                     TreePath path = parent.pathByAddingChild(n);
352                     expandAll(path, expand);
353                 }
354             }
355 
356             if (expand) {
357                 expandPath(parent);
358             } else {
359                 collapsePath(parent);
360             }
361         }
362     }
363 
364     private void loadASTTreeData(TreeNode rootNode) {
365     	astTreeWidget.setModel(new DefaultTreeModel(rootNode));
366     	astTreeWidget.expandAll(true);
367     }
368 
369     private void loadSymbolTableTreeData(TreeNode rootNode) {
370     	if (rootNode != null) {
371 	    	symbolTableTreeWidget.setModel(new DefaultTreeModel(rootNode));
372 	    	symbolTableTreeWidget.expandAll(true);
373     	} else {
374     		symbolTableTreeWidget.setModel(null);
375     	}
376     }
377 
378     private class ShowListener implements ActionListener {
379         public void actionPerformed(ActionEvent ae) {
380             MyPrintStream ps = new MyPrintStream();
381             System.setOut(ps);
382             TreeNode tn;
383             try {
384                 SimpleNode lastCompilationUnit = getCompilationUnit();
385                 tn = new ASTTreeNode(lastCompilationUnit);
386             } catch (ParseException pe) {
387             	tn = new ExceptionNode(pe);
388             	}
389 
390             loadASTTreeData(tn);
391             loadSymbolTableTreeData(null);
392         }
393     }
394 
395     private class DFAListener implements ActionListener {
396         public void actionPerformed(ActionEvent ae) {
397 
398            DFAGraphRule dfaGraphRule = new DFAGraphRule();
399            RuleSet rs = new RuleSet();
400            SourceType sourceType = getSourceType();
401            if (!sourceType.equals(SourceType.JSP)){
402                rs.addRule(dfaGraphRule);
403            }
404            RuleContext ctx = new RuleContext();
405            ctx.setSourceCodeFilename("[no filename]");
406            StringReader reader = new StringReader(codeEditorPane.getText());
407            PMD pmd = new PMD();
408            pmd.setJavaVersion(sourceType);
409 
410            try {
411                 pmd.processFile(reader, rs, ctx);
412 //           } catch (PMDException pmde) {
413 //               loadTreeData(new ExceptionNode(pmde));
414            } catch (Exception e) {
415                e.printStackTrace();
416            		}
417 
418            List<ASTMethodDeclaration> methods = dfaGraphRule.getMethods();
419            if (methods != null && !methods.isEmpty()) {
420                dfaPanel.resetTo(methods, codeEditorPane);
421                dfaPanel.repaint();
422            }
423         }
424     }
425 
426     private class XPathListener implements ActionListener {
427         public void actionPerformed(ActionEvent ae) {
428             xpathResults.clear();
429             if (xpathQueryArea.getText().length() == 0) {
430                 xpathResults.addElement("XPath query field is empty.");
431                 xpathResultList.repaint();
432                 codeEditorPane.requestFocus();
433                 return;
434             }
435             SimpleNode c = getCompilationUnit();
436             try {
437                 XPath xpath = new BaseXPath(xpathQueryArea.getText(), new DocumentNavigator());
438                 for (Iterator iter = xpath.selectNodes(c).iterator(); iter.hasNext();) {
439                     Object obj = iter.next();
440                     if (obj instanceof String) {
441                         System.out.println("Result was a string: " + ((String) obj));
442                     } else if (!(obj instanceof Boolean)) {
443                         // if it's a Boolean and it's 'false', what does that mean?
444                         xpathResults.addElement(obj);
445                     }
446                 }
447                 if (xpathResults.isEmpty()) {
448                     xpathResults.addElement("No matching nodes " + System.currentTimeMillis());
449                 }
450             } catch (ParseException pe) {
451                 xpathResults.addElement(pe.fillInStackTrace().getMessage());
452             } catch (JaxenException je) {
453                 xpathResults.addElement(je.fillInStackTrace().getMessage());
454             }
455             xpathResultList.repaint();
456             xpathQueryArea.requestFocus();
457         }
458     }
459 
460     private class SymbolTableListener implements TreeSelectionListener {
461     	public void valueChanged(TreeSelectionEvent e) {
462     		if (e.getNewLeadSelectionPath() != null) {
463     			ASTTreeNode astTreeNode = (ASTTreeNode)e.getNewLeadSelectionPath().getLastPathComponent();
464 
465     			DefaultMutableTreeNode symbolTableTreeNode = new DefaultMutableTreeNode();
466     			DefaultMutableTreeNode selectedAstTreeNode = new DefaultMutableTreeNode("AST Node: " + astTreeNode.label());
467     			symbolTableTreeNode.add(selectedAstTreeNode);
468 
469 	    		List<Scope> scopes = new ArrayList<Scope>();
470 	    		Scope scope = astTreeNode.getScope();
471 	    		while (scope != null)
472 	    		{
473 	    			scopes.add(scope);
474 	    			scope = scope.getParent();
475 	    		}
476 	    		Collections.reverse(scopes);
477 	    		for (int i = 0; i < scopes.size(); i++) {
478 	    			scope = scopes.get(i);
479 	    			DefaultMutableTreeNode scopeTreeNode =  new DefaultMutableTreeNode("Scope: " + scope.getClass().getSimpleName());
480 	    			selectedAstTreeNode.add(scopeTreeNode);
481 	    			if (!(scope instanceof MethodScope || scope instanceof LocalScope)) {
482 	    				if (!scope.getClassDeclarations().isEmpty()) {
483 				    		for (ClassNameDeclaration classNameDeclaration: scope.getClassDeclarations().keySet()) {
484 				    			DefaultMutableTreeNode classNameDeclarationTreeNode = new DefaultMutableTreeNode("Class name declaration: " + classNameDeclaration);
485 				    			scopeTreeNode.add(classNameDeclarationTreeNode);
486 				    			for (NameOccurrence nameOccurrence: scope.getClassDeclarations().get(classNameDeclaration)) {
487 					    			DefaultMutableTreeNode nameOccurenceTreeNode = new DefaultMutableTreeNode("Name occurrence: " + nameOccurrence);
488 					    			classNameDeclarationTreeNode.add(nameOccurenceTreeNode);
489 				    			}
490 				    		}
491 	    				}
492 	    			}
493 	    			if (scope instanceof ClassScope) {
494 	    				ClassScope classScope = (ClassScope)scope;
495 	    				if (!classScope.getMethodDeclarations().isEmpty()) {
496 				    		for (MethodNameDeclaration methodNameDeclaration: classScope.getMethodDeclarations().keySet()) {
497 				    			DefaultMutableTreeNode methodNameDeclarationTreeNode = new DefaultMutableTreeNode("Method name declaration: " + methodNameDeclaration);
498 				    			scopeTreeNode.add(methodNameDeclarationTreeNode);
499 				    			for (NameOccurrence nameOccurrence: classScope.getMethodDeclarations().get(methodNameDeclaration)) {
500 					    			DefaultMutableTreeNode nameOccurenceTreeNode = new DefaultMutableTreeNode("Name occurrence: " + nameOccurrence);
501 					    			methodNameDeclarationTreeNode.add(nameOccurenceTreeNode);
502 				    			}
503 				    		}
504 	    				}
505 	    			}
506 	    			if (!(scope instanceof SourceFileScope)) {
507 	    				if (!scope.getVariableDeclarations().isEmpty()) {
508 				    		for (VariableNameDeclaration variableNameDeclaration: scope.getVariableDeclarations().keySet()) {
509 				    			DefaultMutableTreeNode variableNameDeclarationTreeNode = new DefaultMutableTreeNode("Variable name declaration: " + variableNameDeclaration);
510 				    			scopeTreeNode.add(variableNameDeclarationTreeNode);
511 				    			for (NameOccurrence nameOccurrence: scope.getVariableDeclarations().get(variableNameDeclaration)) {
512 					    			DefaultMutableTreeNode nameOccurenceTreeNode = new DefaultMutableTreeNode("Name occurrence: " + nameOccurrence);
513 					    			variableNameDeclarationTreeNode.add(nameOccurenceTreeNode);
514 				    			}
515 				    		}
516 	    				}
517 	    			}
518 	    		}
519 	    		loadSymbolTableTreeData(symbolTableTreeNode);
520     		}
521         }
522     }
523 
524     private class CodeHighlightListener implements TreeSelectionListener {
525         public void valueChanged(TreeSelectionEvent e) {
526             if (e.getNewLeadSelectionPath() != null) {
527                 ASTTreeNode selected = (ASTTreeNode)e.getNewLeadSelectionPath().getLastPathComponent();
528                 if (selected != null && selected.node instanceof SimpleNode) {
529                     SimpleNode node = (SimpleNode) selected.node;
530 
531                     codeEditorPane.select(node);
532                 }
533             }
534         }
535     }
536 
537     private class ASTListCellRenderer extends JLabel implements ListCellRenderer {
538         private static final long serialVersionUID = 1L;
539 
540         public Component getListCellRendererComponent(
541                 JList list,
542                 Object value,
543                 int index,
544                 boolean isSelected,
545                 boolean cellHasFocus) {
546 
547             if (isSelected) {
548                 setBackground(list.getSelectionBackground());
549                 setForeground(list.getSelectionForeground());
550             } else {
551                 setBackground(list.getBackground());
552                 setForeground(list.getForeground());
553             }
554 
555             String text;
556             if (value instanceof SimpleNode) {
557                 SimpleNode node = (SimpleNode) value;
558                 StringBuffer sb = new StringBuffer();
559                 String name = node.getClass().getName().substring(node.getClass().getName().lastIndexOf('.') + 1);
560                 sb.append(name)
561                     .append(" at line ").append(node.getBeginLine())
562                     .append(" column ").append(node.getBeginColumn())
563                     .append(PMD.EOL);
564                 text = sb.toString();
565             } else {
566                 text = value.toString();
567             }
568             setText(text);
569             return this;
570         }
571     }
572 
573     private class ASTSelectionListener implements ListSelectionListener {
574         public void valueChanged(ListSelectionEvent e) {
575             ListSelectionModel lsm = (ListSelectionModel)e.getSource();
576             if (!lsm.isSelectionEmpty()) {
577                 Object o = xpathResults.get(lsm.getMinSelectionIndex());
578                 if (o instanceof SimpleNode) {
579                     codeEditorPane.select((SimpleNode) o);
580                 }
581             }
582         }
583     }
584 
585 	private boolean exitOnClose = true;
586     private final CodeEditorTextPane codeEditorPane = new CodeEditorTextPane();
587     private final TreeWidget astTreeWidget			= new TreeWidget(new Object[0]);
588     private DefaultListModel xpathResults			= new DefaultListModel();
589     private final JList xpathResultList				= new JList(xpathResults);
590     private final JTextArea xpathQueryArea			= new JTextArea(15, 30);
591     private final TreeWidget symbolTableTreeWidget	= new TreeWidget(new Object[0]);
592     private final JFrame frame 						= new JFrame("PMD Rule Designer (v " + PMD.VERSION + ')');
593     private final DFAPanel dfaPanel					= new DFAPanel();
594     private final JRadioButtonMenuItem[] sourceTypeMenuItems = new JRadioButtonMenuItem[sourceTypeSets.length];
595 
596 	public Designer(String[] args) {
597 		if (args.length > 0) {
598 			exitOnClose = !args[0].equals("-noexitonclose");
599 		}
600 
601 		MatchesFunction.registerSelfInSimpleContext();
602         TypeOfFunction.registerSelfInSimpleContext();
603 
604         xpathQueryArea.setFont(new Font("Verdana", Font.PLAIN, 16));
605         JSplitPane controlSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, createCodeEditorPanel(), createXPathQueryPanel());
606 
607         JSplitPane astAndSymbolTablePane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, createASTPanel(), createSymbolTableResultPanel());
608 
609         JSplitPane resultsSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, astAndSymbolTablePane, createXPathResultPanel());
610 
611         JTabbedPane tabbed = new JTabbedPane();
612         tabbed.addTab("Abstract Syntax Tree / XPath / Symbol Table", resultsSplitPane);
613         tabbed.addTab("Data Flow Analysis", dfaPanel);
614         try {
615             // Remove when minimal runtime support is >= JDK 1.4
616             Method setMnemonicAt = JTabbedPane.class.getMethod("setMnemonicAt", new Class[]{Integer.TYPE, Integer.TYPE});
617             if (setMnemonicAt != null) {
618                 //        // Compatible with >= JDK 1.4
619                 //        tabbed.setMnemonicAt(0, KeyEvent.VK_A);
620                 //        tabbed.setMnemonicAt(1, KeyEvent.VK_D);
621                 setMnemonicAt.invoke(tabbed, new Object[]{NumericConstants.ZERO, KeyEvent.VK_A});
622                 setMnemonicAt.invoke(tabbed, new Object[]{NumericConstants.ONE, KeyEvent.VK_D});
623             }
624         } catch (NoSuchMethodException nsme) { // Runtime is < JDK 1.4
625         } catch (IllegalAccessException e) { // Runtime is >= JDK 1.4 but there was an error accessing the function
626             e.printStackTrace();
627             throw new InternalError("Runtime reports to be >= JDK 1.4 yet String.split(java.lang.String) is broken.");
628         } catch (IllegalArgumentException e) {
629             e.printStackTrace();
630             throw new InternalError("Runtime reports to be >= JDK 1.4 yet String.split(java.lang.String) is broken.");
631         } catch (InvocationTargetException e) { // Runtime is >= JDK 1.4 but there was an error accessing the function
632             e.printStackTrace();
633             throw new InternalError("Runtime reports to be >= JDK 1.4 yet String.split(java.lang.String) is broken.");
634         }
635 
636         JSplitPane containerSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, controlSplitPane, tabbed);
637         containerSplitPane.setContinuousLayout(true);
638 
639         JMenuBar menuBar = createMenuBar();
640         frame.setJMenuBar(menuBar);
641         frame.getContentPane().add(containerSplitPane);
642         frame.setDefaultCloseOperation(exitOnClose ? JFrame.EXIT_ON_CLOSE : JFrame.DISPOSE_ON_CLOSE);
643 
644         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
645         int screenHeight = screenSize.height;
646         int screenWidth = screenSize.width;
647 
648         frame.pack();
649         frame.setSize((screenWidth*3/4),(screenHeight*3/4));
650         frame.setLocation((screenWidth -frame.getWidth()) / 2, (screenHeight  - frame.getHeight()) / 2);
651         frame.setVisible(true);
652         int horozontalMiddleLocation = controlSplitPane.getMaximumDividerLocation() * 3 / 5;
653         controlSplitPane.setDividerLocation(horozontalMiddleLocation);
654         containerSplitPane.setDividerLocation(containerSplitPane.getMaximumDividerLocation() / 2);
655         astAndSymbolTablePane.setDividerLocation(astAndSymbolTablePane.getMaximumDividerLocation()/3);
656         resultsSplitPane.setDividerLocation(horozontalMiddleLocation);
657     }
658 
659     private JMenuBar createMenuBar() {
660         JMenuBar menuBar = new JMenuBar();
661         JMenu menu = new JMenu("JDK");
662         ButtonGroup group = new ButtonGroup();
663 
664         for (int i=0; i<sourceTypeSets.length; i++) {
665         	JRadioButtonMenuItem button = new JRadioButtonMenuItem(sourceTypeSets[i][0].toString());
666         	sourceTypeMenuItems[i] = button;
667         	group.add(button);
668         	menu.add(button);
669         }
670         sourceTypeMenuItems[defaultSourceTypeSelectionIndex].setSelected(true);
671         menuBar.add(menu);
672 
673         JMenu actionsMenu = new JMenu("Actions");
674         JMenuItem copyXMLItem = new JMenuItem("Copy xml to clipboard");
675         copyXMLItem.addActionListener(new ActionListener() {
676             public void actionPerformed(ActionEvent e) {
677                 copyXmlToClipboard();
678             }
679         });
680         actionsMenu.add(copyXMLItem);
681         JMenuItem createRuleXMLItem = new JMenuItem("Create rule XML");
682         createRuleXMLItem.addActionListener(new ActionListener() {
683             public void actionPerformed(ActionEvent e) {
684                 createRuleXML();
685             }
686         });
687         actionsMenu.add(createRuleXMLItem);
688         menuBar.add(actionsMenu);
689 
690         return menuBar;
691     }
692 
693     private void createRuleXML() {
694     	CreateXMLRulePanel rulePanel = new CreateXMLRulePanel(xpathQueryArea, codeEditorPane);
695     	JFrame xmlframe = new JFrame("Create XML Rule");
696     	xmlframe.setContentPane(rulePanel);
697     	xmlframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
698         xmlframe.setSize(new Dimension(600, 700));
699         xmlframe.addComponentListener(new java.awt.event.ComponentAdapter() {
700         	  public void componentResized(ComponentEvent e) {
701         	    JFrame tmp = (JFrame)e.getSource();
702         	    if (tmp.getWidth()<600 || tmp.getHeight()<700) {
703         	      tmp.setSize(600, 700);
704         	    }
705         	  }
706         	});
707         int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
708         int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;
709         xmlframe.pack();
710         xmlframe.setLocation((screenWidth - xmlframe.getWidth()) / 2, (screenHeight - xmlframe.getHeight()) / 2);
711         xmlframe.setVisible(true);
712     }
713 
714     private JComponent createCodeEditorPanel()
715     {
716         JPanel p = new JPanel();
717         p.setLayout(new BorderLayout());
718         codeEditorPane.setBorder(BorderFactory.createLineBorder(Color.black));
719         makeTextComponentUndoable(codeEditorPane);
720 
721         p.add(new JLabel("Source code:"), BorderLayout.NORTH);
722         p.add(new JScrollPane(codeEditorPane), BorderLayout.CENTER);
723 
724         return p;
725     }
726 
727     private JComponent createASTPanel() {
728     	astTreeWidget.setCellRenderer(createNoImageTreeCellRenderer());
729     	TreeSelectionModel model = astTreeWidget.getSelectionModel();
730     	model.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
731     	model.addTreeSelectionListener(new SymbolTableListener());
732     	model.addTreeSelectionListener(new CodeHighlightListener());
733         return new JScrollPane(astTreeWidget);
734     }
735 
736     private JComponent createXPathResultPanel() {
737         xpathResults.addElement("No XPath results yet, run an XPath Query first.");
738         xpathResultList.setBorder(BorderFactory.createLineBorder(Color.black));
739         xpathResultList.setFixedCellWidth(300);
740         xpathResultList.setCellRenderer(new ASTListCellRenderer());
741         xpathResultList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
742         xpathResultList.getSelectionModel().addListSelectionListener(new ASTSelectionListener());
743         JScrollPane scrollPane = new JScrollPane();
744         scrollPane.getViewport().setView(xpathResultList);
745         return scrollPane;
746     }
747 
748     private JPanel createXPathQueryPanel() {
749         JPanel p = new JPanel();
750         p.setLayout(new BorderLayout());
751         xpathQueryArea.setBorder(BorderFactory.createLineBorder(Color.black));
752         makeTextComponentUndoable(xpathQueryArea);
753         JScrollPane scrollPane = new JScrollPane(xpathQueryArea);
754         scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
755         scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
756         final JButton b = createGoButton();
757 
758         p.add(new JLabel("XPath Query (if any):"), BorderLayout.NORTH);
759         p.add(scrollPane, BorderLayout.CENTER);
760         p.add(b, BorderLayout.SOUTH);
761 
762         return p;
763     }
764 
765     private JComponent createSymbolTableResultPanel() {
766     	symbolTableTreeWidget.setCellRenderer(createNoImageTreeCellRenderer());
767         return new JScrollPane(symbolTableTreeWidget);
768     }
769 
770     private JButton createGoButton() {
771         JButton b = new JButton("Go");
772         b.setMnemonic('g');
773         b.addActionListener(new ShowListener());
774         b.addActionListener(codeEditorPane);
775         b.addActionListener(new XPathListener());
776         b.addActionListener(new DFAListener());
777         return b;
778     }
779 
780     private static void makeTextComponentUndoable(JTextComponent textConponent) {
781         final UndoManager undoManager = new UndoManager();
782         textConponent.getDocument().addUndoableEditListener(new UndoableEditListener() {
783 			     public void undoableEditHappened(
784 			       UndoableEditEvent evt) {
785 			         undoManager.addEdit(evt.getEdit());
786 			     }
787   			 });
788         ActionMap actionMap = textConponent.getActionMap();
789         InputMap inputMap = textConponent.getInputMap();
790         actionMap.put("Undo", new AbstractAction("Undo") {
791 		         public void actionPerformed(ActionEvent evt) {
792 		             try {
793 		                 if (undoManager.canUndo()) {
794 		                     undoManager.undo();
795 		                 }
796 		             } catch (CannotUndoException e) {
797 		             }
798 		         }
799         	 });
800         inputMap.put(KeyStroke.getKeyStroke("control Z"), "Undo");
801 
802         actionMap.put("Redo", new AbstractAction("Redo") {
803 			    public void actionPerformed(ActionEvent evt) {
804 			        try {
805 			            if (undoManager.canRedo()) {
806 			                undoManager.redo();
807 			            }
808 			        } catch (CannotRedoException e) {
809 			        }
810 			    }
811             });
812         inputMap.put(KeyStroke.getKeyStroke("control Y"), "Redo");
813     }
814 
815     public static void main(String[] args) {
816         new Designer(args);
817     }
818 
819     private final void copyXmlToClipboard() {
820         if (codeEditorPane.getText() != null && codeEditorPane.getText().trim().length() > 0) {
821             String xml = "";
822             SimpleNode cu = getCompilationUnit();
823             if (cu != null) {
824                 try {
825                     xml = getXmlString(cu);
826                 } catch (TransformerException e) {
827                     e.printStackTrace();
828                     xml = "Error trying to construct XML representation";
829                 }
830             }
831             Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(xml), this);
832         }
833     }
834 
835     /**
836      * Returns an unformatted xml string (without the declaration)
837      *
838      * @throws TransformerException if the XML cannot be converted to a string
839      */
840     private String getXmlString(SimpleNode node) throws TransformerException {
841         StringWriter writer = new StringWriter();
842 
843         Source source = new DOMSource(node.asXml());
844         Result result = new StreamResult(writer);
845         TransformerFactory transformerFactory = TransformerFactory.newInstance();
846         try {
847             transformerFactory.setAttribute("indent-number", 4);   //For java 5
848         } catch (IllegalArgumentException e) {
849             //Running on Java 1.4 which does not support this attribute
850         }
851         Transformer xformer = transformerFactory.newTransformer();
852         xformer.setOutputProperty(OutputKeys.INDENT, "yes");
853         xformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "4");   //For java 1.4
854         xformer.transform(source, result);
855 
856         return writer.toString();
857     }
858 
859     public void lostOwnership(Clipboard clipboard, Transferable contents) {
860     }
861 }
862