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.File;
22  import java.io.FileInputStream;
23  import java.io.FileWriter;
24  import java.io.IOException;
25  import java.io.StringReader;
26  import java.io.StringWriter;
27  import java.lang.reflect.Proxy;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.Enumeration;
32  import java.util.LinkedList;
33  import java.util.List;
34  
35  import javax.swing.AbstractAction;
36  import javax.swing.AbstractButton;
37  import javax.swing.ActionMap;
38  import javax.swing.BorderFactory;
39  import javax.swing.ButtonGroup;
40  import javax.swing.DefaultListModel;
41  import javax.swing.InputMap;
42  import javax.swing.JButton;
43  import javax.swing.JComponent;
44  import javax.swing.JFrame;
45  import javax.swing.JLabel;
46  import javax.swing.JList;
47  import javax.swing.JMenu;
48  import javax.swing.JMenuBar;
49  import javax.swing.JMenuItem;
50  import javax.swing.JPanel;
51  import javax.swing.JRadioButton;
52  import javax.swing.JRadioButtonMenuItem;
53  import javax.swing.JScrollPane;
54  import javax.swing.JSplitPane;
55  import javax.swing.JTabbedPane;
56  import javax.swing.JTextArea;
57  import javax.swing.JTree;
58  import javax.swing.KeyStroke;
59  import javax.swing.ListCellRenderer;
60  import javax.swing.ListSelectionModel;
61  import javax.swing.ScrollPaneConstants;
62  import javax.swing.WindowConstants;
63  import javax.swing.event.ListSelectionEvent;
64  import javax.swing.event.ListSelectionListener;
65  import javax.swing.event.TreeSelectionEvent;
66  import javax.swing.event.TreeSelectionListener;
67  import javax.swing.event.UndoableEditEvent;
68  import javax.swing.event.UndoableEditListener;
69  import javax.swing.text.JTextComponent;
70  import javax.swing.tree.DefaultMutableTreeNode;
71  import javax.swing.tree.DefaultTreeCellRenderer;
72  import javax.swing.tree.DefaultTreeModel;
73  import javax.swing.tree.TreeCellRenderer;
74  import javax.swing.tree.TreeNode;
75  import javax.swing.tree.TreePath;
76  import javax.swing.tree.TreeSelectionModel;
77  import javax.swing.undo.CannotRedoException;
78  import javax.swing.undo.CannotUndoException;
79  import javax.swing.undo.UndoManager;
80  import javax.xml.parsers.DocumentBuilder;
81  import javax.xml.parsers.DocumentBuilderFactory;
82  import javax.xml.parsers.ParserConfigurationException;
83  import javax.xml.transform.OutputKeys;
84  import javax.xml.transform.Result;
85  import javax.xml.transform.Source;
86  import javax.xml.transform.Transformer;
87  import javax.xml.transform.TransformerException;
88  import javax.xml.transform.TransformerFactory;
89  import javax.xml.transform.dom.DOMSource;
90  import javax.xml.transform.stream.StreamResult;
91  
92  import net.sourceforge.pmd.PMD;
93  import net.sourceforge.pmd.PMDConfiguration;
94  import net.sourceforge.pmd.RuleContext;
95  import net.sourceforge.pmd.RuleSet;
96  import net.sourceforge.pmd.RuleSets;
97  import net.sourceforge.pmd.SourceCodeProcessor;
98  import net.sourceforge.pmd.lang.Language;
99  import net.sourceforge.pmd.lang.LanguageVersion;
100 import net.sourceforge.pmd.lang.LanguageVersionHandler;
101 import net.sourceforge.pmd.lang.Parser;
102 import net.sourceforge.pmd.lang.ast.Node;
103 import net.sourceforge.pmd.lang.ast.xpath.Attribute;
104 import net.sourceforge.pmd.lang.ast.xpath.AttributeAxisIterator;
105 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
106 import net.sourceforge.pmd.lang.java.ast.AccessNode;
107 import net.sourceforge.pmd.lang.java.ast.JavaNode;
108 import net.sourceforge.pmd.lang.java.ast.ParseException;
109 import net.sourceforge.pmd.lang.java.symboltable.ClassNameDeclaration;
110 import net.sourceforge.pmd.lang.java.symboltable.ClassScope;
111 import net.sourceforge.pmd.lang.java.symboltable.LocalScope;
112 import net.sourceforge.pmd.lang.java.symboltable.MethodNameDeclaration;
113 import net.sourceforge.pmd.lang.java.symboltable.MethodScope;
114 import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
115 import net.sourceforge.pmd.lang.java.symboltable.Scope;
116 import net.sourceforge.pmd.lang.java.symboltable.SourceFileScope;
117 import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
118 import net.sourceforge.pmd.lang.rule.XPathRule;
119 import net.sourceforge.pmd.lang.xpath.Initializer;
120 import net.sourceforge.pmd.util.StringUtil;
121 
122 import org.w3c.dom.Document;
123 import org.w3c.dom.Element;
124 import org.w3c.dom.Text;
125 import org.xml.sax.SAXException;
126 
127 public class Designer implements ClipboardOwner {
128 
129 	private static final int DEFAULT_LANGUAGE_VERSION_SELECTION_INDEX = Arrays.asList(getSupportedLanguageVersions())
130 	.indexOf(Language.JAVA.getDefaultVersion());
131 
132 	private Node getCompilationUnit() {
133 		LanguageVersionHandler languageVersionHandler = getLanguageVersionHandler();
134 		Parser parser = languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions());
135 		Node node = parser.parse(null, new StringReader(codeEditorPane.getText()));
136 		languageVersionHandler.getSymbolFacade().start(node);
137 		languageVersionHandler.getTypeResolutionFacade(null).start(node);
138 		return node;
139 	}
140 
141 	private static LanguageVersion[] getSupportedLanguageVersions() {
142 		List<LanguageVersion> languageVersions = new ArrayList<LanguageVersion>();
143 		for (LanguageVersion languageVersion : LanguageVersion.values()) {
144 			LanguageVersionHandler languageVersionHandler = languageVersion.getLanguageVersionHandler();
145 			if (languageVersionHandler != null) {
146 				Parser parser = languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions());
147 				if (parser != null && parser.canParse()) {
148 					languageVersions.add(languageVersion);
149 				}
150 			}
151 		}
152 		return languageVersions.toArray(new LanguageVersion[languageVersions.size()]);
153 	}
154 
155 	private LanguageVersion getLanguageVersion() {
156 		return getSupportedLanguageVersions()[selectedLanguageVersionIndex()];
157 	}
158 
159 	private void setLanguageVersion(LanguageVersion languageVersion) {
160 		if (languageVersion != null) {
161 			LanguageVersion[] versions = getSupportedLanguageVersions();
162 			for (int i = 0; i < versions.length; i++) {
163 				LanguageVersion version = versions[i];
164 				if (languageVersion.equals(version)) {
165 					languageVersionMenuItems[i].setSelected(true);
166 					break;
167 				}
168 			}
169 		}
170 	}
171 
172 	private int selectedLanguageVersionIndex() {
173 		for (int i = 0; i < languageVersionMenuItems.length; i++) {
174 			if (languageVersionMenuItems[i].isSelected()) {
175 				return i;
176 			}
177 		}
178 		throw new RuntimeException("Initial default language version not specified");
179 	}
180 
181 	private LanguageVersionHandler getLanguageVersionHandler() {
182 		LanguageVersion languageVersion = getLanguageVersion();
183 		return languageVersion.getLanguageVersionHandler();
184 	}
185 
186 	private class ExceptionNode implements TreeNode {
187 
188 		private Object item;
189 		private ExceptionNode[] kids;
190 
191 		public ExceptionNode(Object theItem) {
192 			item = theItem;
193 
194 			if (item instanceof ParseException) {
195 				createKids();
196 			}
197 		}
198 
199 		// each line in the error message becomes a separate tree node
200 		private void createKids() {
201 
202 			String message = ((ParseException) item).getMessage();
203 			String[] lines = StringUtil.substringsOf(message, PMD.EOL);
204 
205 			kids = new ExceptionNode[lines.length];
206 			for (int i = 0; i < lines.length; i++) {
207 				kids[i] = new ExceptionNode(lines[i]);
208 			}
209 		}
210 
211 		public int getChildCount() {
212 			return kids == null ? 0 : kids.length;
213 		}
214 
215 		public boolean getAllowsChildren() {
216 			return false;
217 		}
218 
219 		public boolean isLeaf() {
220 			return kids == null;
221 		}
222 
223 		public TreeNode getParent() {
224 			return null;
225 		}
226 
227 		public TreeNode getChildAt(int childIndex) {
228 			return kids[childIndex];
229 		}
230 
231 		public String label() {
232 			return item.toString();
233 		}
234 
235 		public Enumeration<ExceptionNode> children() {
236 			Enumeration<ExceptionNode> e = new Enumeration<ExceptionNode>() {
237 				int i = 0;
238 
239 				public boolean hasMoreElements() {
240 					return kids != null && i < kids.length;
241 				}
242 
243 				public ExceptionNode nextElement() {
244 					return kids[i++];
245 				}
246 			};
247 			return e;
248 		}
249 
250 		public int getIndex(TreeNode node) {
251 			for (int i = 0; i < kids.length; i++) {
252 				if (kids[i] == node) {
253 					return i;
254 				}
255 			}
256 			return -1;
257 		}
258 	}
259 
260 	// Tree node that wraps the AST node for the tree widget and
261 	// any possible children they may have.
262 	private class ASTTreeNode implements TreeNode {
263 
264 		private Node node;
265 		private ASTTreeNode parent;
266 		private ASTTreeNode[] kids;
267 
268 		public ASTTreeNode(Node theNode) {
269 			node = theNode;
270 
271 			Node parent = node.jjtGetParent();
272 			if (parent != null) {
273 				this.parent = new ASTTreeNode(parent);
274 			}
275 		}
276 
277 		private ASTTreeNode(ASTTreeNode parent, Node theNode) {
278 			node = theNode;
279 			this.parent = parent;
280 		}
281 
282 		public int getChildCount() {
283 			return node.jjtGetNumChildren();
284 		}
285 
286 		public boolean getAllowsChildren() {
287 			return false;
288 		}
289 
290 		public boolean isLeaf() {
291 			return node.jjtGetNumChildren() == 0;
292 		}
293 
294 		public TreeNode getParent() {
295 			return parent;
296 		}
297 
298 		public Scope getScope() {
299 			if (node instanceof JavaNode) {
300 				return ((JavaNode) node).getScope();
301 			}
302 			return null;
303 		}
304 
305 		public Enumeration<ASTTreeNode> children() {
306 
307 			if (getChildCount() > 0) {
308 				getChildAt(0); // force it to build kids
309 			}
310 
311 			Enumeration<ASTTreeNode> e = new Enumeration<ASTTreeNode>() {
312 				int i = 0;
313 
314 				public boolean hasMoreElements() {
315 					return kids != null && i < kids.length;
316 				}
317 
318 				public ASTTreeNode nextElement() {
319 					return kids[i++];
320 				}
321 			};
322 			return e;
323 		}
324 
325 		public TreeNode getChildAt(int childIndex) {
326 
327 			if (kids == null) {
328 				kids = new ASTTreeNode[node.jjtGetNumChildren()];
329 				for (int i = 0; i < kids.length; i++) {
330 					kids[i] = new ASTTreeNode(this.parent, node.jjtGetChild(i));
331 				}
332 			}
333 			return kids[childIndex];
334 		}
335 
336 		public int getIndex(TreeNode node) {
337 
338 			for (int i = 0; i < kids.length; i++) {
339 				if (kids[i] == node) {
340 					return i;
341 				}
342 			}
343 			return -1;
344 		}
345 
346 		public String label() {
347 			LanguageVersionHandler languageVersionHandler = getLanguageVersionHandler();
348 			StringWriter writer = new StringWriter();
349 			languageVersionHandler.getDumpFacade(writer, "", false).start(node);
350 			return writer.toString();
351 		}
352 
353 		public String getToolTipText() {
354 			String tooltip = "Line: " + node.getBeginLine() + " Column: " + node.getBeginColumn();
355 
356 			if (node instanceof AccessNode) {
357 				AccessNode accessNode = (AccessNode) node;
358 				if (!"".equals(tooltip)) {
359 					tooltip += ",";
360 				}
361 				tooltip += accessNode.isAbstract() ? " Abstract" : "";
362 				tooltip += accessNode.isStatic() ? " Static" : "";
363 				tooltip += accessNode.isFinal() ? " Final" : "";
364 				tooltip += accessNode.isNative() ? " Native" : "";
365 				tooltip += accessNode.isPrivate() ? " Private" : "";
366 				tooltip += accessNode.isSynchronized() ? " Synchronised" : "";
367 				tooltip += accessNode.isTransient() ? " Transient" : "";
368 				tooltip += accessNode.isVolatile() ? " Volatile" : "";
369 				tooltip += accessNode.isStrictfp() ? " Strictfp" : "";
370 			}
371 			return tooltip;
372 		}
373 
374 		public List<String> getAttributes() {
375 			List<String> result = new LinkedList<String>();
376 			AttributeAxisIterator attributeAxisIterator = new AttributeAxisIterator(node);
377 			while (attributeAxisIterator.hasNext()) {
378 				Attribute attribute = attributeAxisIterator.next();
379 				result.add(attribute.getName() + "=" + attribute.getStringValue());
380 			}
381 			return result;
382 		}
383 	}
384 
385 	private TreeCellRenderer createNoImageTreeCellRenderer() {
386 		DefaultTreeCellRenderer treeCellRenderer = new DefaultTreeCellRenderer();
387 		treeCellRenderer.setLeafIcon(null);
388 		treeCellRenderer.setOpenIcon(null);
389 		treeCellRenderer.setClosedIcon(null);
390 		return treeCellRenderer;
391 	}
392 
393 	// Special tree variant that knows how to retrieve node labels and
394 	// provides the ability to expand all nodes at once.
395 	private class TreeWidget extends JTree {
396 
397 		private static final long serialVersionUID = 1L;
398 
399 		public TreeWidget(Object[] items) {
400 			super(items);
401 			setToolTipText("");
402 		}
403 
404 		@Override
405 		public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row,
406 				boolean hasFocus) {
407 			if (value == null) {
408 				return "";
409 			}
410 			if (value instanceof ASTTreeNode) {
411 				return ((ASTTreeNode) value).label();
412 			}
413 			if (value instanceof ExceptionNode) {
414 				return ((ExceptionNode) value).label();
415 			}
416 			return value.toString();
417 		}
418 
419 		@Override
420 		public String getToolTipText(MouseEvent e) {
421 			if (getRowForLocation(e.getX(), e.getY()) == -1) {
422 				return null;
423 			}
424 			TreePath curPath = getPathForLocation(e.getX(), e.getY());
425 			if (curPath.getLastPathComponent() instanceof ASTTreeNode) {
426 				return ((ASTTreeNode) curPath.getLastPathComponent()).getToolTipText();
427 			} else {
428 				return super.getToolTipText(e);
429 			}
430 		}
431 
432 		public void expandAll(boolean expand) {
433 			TreeNode root = (TreeNode) getModel().getRoot();
434 			expandAll(new TreePath(root), expand);
435 		}
436 
437 		private void expandAll(TreePath parent, boolean expand) {
438 			// Traverse children
439 			TreeNode node = (TreeNode) parent.getLastPathComponent();
440 			if (node.getChildCount() >= 0) {
441 				for (Enumeration<TreeNode> e = node.children(); e.hasMoreElements();) {
442 					TreeNode n = e.nextElement();
443 					TreePath path = parent.pathByAddingChild(n);
444 					expandAll(path, expand);
445 				}
446 			}
447 
448 			if (expand) {
449 				expandPath(parent);
450 			} else {
451 				collapsePath(parent);
452 			}
453 		}
454 	}
455 
456 	private void loadASTTreeData(TreeNode rootNode) {
457 		astTreeWidget.setModel(new DefaultTreeModel(rootNode));
458 		astTreeWidget.setRootVisible(true);
459 		astTreeWidget.expandAll(true);
460 	}
461 
462 	private void loadSymbolTableTreeData(TreeNode rootNode) {
463 		if (rootNode != null) {
464 			symbolTableTreeWidget.setModel(new DefaultTreeModel(rootNode));
465 			symbolTableTreeWidget.expandAll(true);
466 		} else {
467 			symbolTableTreeWidget.setModel(null);
468 		}
469 	}
470 
471 	private class ShowListener implements ActionListener {
472 		public void actionPerformed(ActionEvent ae) {
473 			TreeNode tn;
474 			try {
475 				Node lastCompilationUnit = getCompilationUnit();
476 				tn = new ASTTreeNode(lastCompilationUnit);
477 			} catch (ParseException pe) {
478 				tn = new ExceptionNode(pe);
479 			}
480 
481 			loadASTTreeData(tn);
482 			loadSymbolTableTreeData(null);
483 		}
484 	}
485 
486 	private class DFAListener implements ActionListener {
487 		public void actionPerformed(ActionEvent ae) {
488 
489 			DFAGraphRule dfaGraphRule = new DFAGraphRule();
490 			RuleSet rs = new RuleSet();
491 			LanguageVersion languageVersion = getLanguageVersion();
492 			if (languageVersion.getLanguage().equals(Language.JAVA)) {
493 				rs.addRule(dfaGraphRule);
494 			}
495 			RuleContext ctx = new RuleContext();
496 			ctx.setSourceCodeFilename("[no filename]." + languageVersion.getLanguage().getExtensions().get(0));
497 			StringReader reader = new StringReader(codeEditorPane.getText());
498 			PMDConfiguration config = new PMDConfiguration();
499 			config.setDefaultLanguageVersion(languageVersion);
500 
501 			try {
502 				new SourceCodeProcessor(config).processSourceCode(reader, new RuleSets(rs), ctx);
503 				//	    } catch (PMDException pmde) {
504 				//		loadTreeData(new ExceptionNode(pmde));
505 			} catch (Exception e) {
506 				e.printStackTrace();
507 			}
508 
509 			List<ASTMethodDeclaration> methods = dfaGraphRule.getMethods();
510 			if (methods != null && !methods.isEmpty()) {
511 				dfaPanel.resetTo(methods, codeEditorPane);
512 				dfaPanel.repaint();
513 			}
514 		}
515 	}
516 
517 	private class XPathListener implements ActionListener {
518 		public void actionPerformed(ActionEvent ae) {
519 			xpathResults.clear();
520 			if (StringUtil.isEmpty(xpathQueryArea.getText())) {
521 				xpathResults.addElement("XPath query field is empty.");
522 				xpathResultList.repaint();
523 				codeEditorPane.requestFocus();
524 				return;
525 			}
526 			Node c = getCompilationUnit();
527 			try {
528 				XPathRule xpathRule = new XPathRule() {
529 					@Override
530 					public void addViolation(Object data, Node node, String arg) {
531 						xpathResults.addElement(node);
532 					}
533 				};
534 				xpathRule.setMessage("");
535 				xpathRule.setLanguage(getLanguageVersion().getLanguage());
536 				xpathRule.setXPath(xpathQueryArea.getText());
537 				xpathRule.setVersion(xpathVersionButtonGroup.getSelection().getActionCommand());
538 
539 				RuleSet ruleSet = new RuleSet();
540 				ruleSet.addRule(xpathRule);
541 
542 				RuleSets ruleSets = new RuleSets(ruleSet);
543 
544 				RuleContext ruleContext = new RuleContext();
545 				ruleContext.setLanguageVersion(getLanguageVersion());
546 
547 				List<Node> nodes = new ArrayList<Node>();
548 				nodes.add(c);
549 				ruleSets.apply(nodes, ruleContext, xpathRule.getLanguage());
550 
551 				if (xpathResults.isEmpty()) {
552 					xpathResults.addElement("No matching nodes " + System.currentTimeMillis());
553 				}
554 			} catch (ParseException pe) {
555 				xpathResults.addElement(pe.fillInStackTrace().getMessage());
556 			}
557 			xpathResultList.repaint();
558 			xpathQueryArea.requestFocus();
559 		}
560 	}
561 
562 	private class SymbolTableListener implements TreeSelectionListener {
563 		public void valueChanged(TreeSelectionEvent e) {
564 			if (e.getNewLeadSelectionPath() != null) {
565 				ASTTreeNode astTreeNode = (ASTTreeNode) e.getNewLeadSelectionPath().getLastPathComponent();
566 
567 				DefaultMutableTreeNode symbolTableTreeNode = new DefaultMutableTreeNode();
568 				DefaultMutableTreeNode selectedAstTreeNode = new DefaultMutableTreeNode("AST Node: "
569 						+ astTreeNode.label());
570 				symbolTableTreeNode.add(selectedAstTreeNode);
571 
572 				List<Scope> scopes = new ArrayList<Scope>();
573 				Scope scope = astTreeNode.getScope();
574 				while (scope != null) {
575 					scopes.add(scope);
576 					scope = scope.getParent();
577 				}
578 				Collections.reverse(scopes);
579 				for (int i = 0; i < scopes.size(); i++) {
580 					scope = scopes.get(i);
581 					DefaultMutableTreeNode scopeTreeNode = new DefaultMutableTreeNode("Scope: "
582 							+ scope.getClass().getSimpleName());
583 					selectedAstTreeNode.add(scopeTreeNode);
584 					if (!(scope instanceof MethodScope || scope instanceof LocalScope)) {
585 						if (!scope.getClassDeclarations().isEmpty()) {
586 							for (ClassNameDeclaration classNameDeclaration : scope.getClassDeclarations().keySet()) {
587 								DefaultMutableTreeNode classNameDeclarationTreeNode = new DefaultMutableTreeNode(
588 										"Class name declaration: " + classNameDeclaration);
589 								scopeTreeNode.add(classNameDeclarationTreeNode);
590 								for (NameOccurrence nameOccurrence : scope.getClassDeclarations().get(
591 										classNameDeclaration)) {
592 									DefaultMutableTreeNode nameOccurenceTreeNode = new DefaultMutableTreeNode(
593 											"Name occurrence: " + nameOccurrence);
594 									classNameDeclarationTreeNode.add(nameOccurenceTreeNode);
595 								}
596 							}
597 						}
598 					}
599 					if (scope instanceof ClassScope) {
600 						ClassScope classScope = (ClassScope) scope;
601 						if (!classScope.getMethodDeclarations().isEmpty()) {
602 							for (MethodNameDeclaration methodNameDeclaration : classScope.getMethodDeclarations()
603 									.keySet()) {
604 								DefaultMutableTreeNode methodNameDeclarationTreeNode = new DefaultMutableTreeNode(
605 										"Method name declaration: " + methodNameDeclaration);
606 								scopeTreeNode.add(methodNameDeclarationTreeNode);
607 								for (NameOccurrence nameOccurrence : classScope.getMethodDeclarations().get(
608 										methodNameDeclaration)) {
609 									DefaultMutableTreeNode nameOccurenceTreeNode = new DefaultMutableTreeNode(
610 											"Name occurrence: " + nameOccurrence);
611 									methodNameDeclarationTreeNode.add(nameOccurenceTreeNode);
612 								}
613 							}
614 						}
615 					}
616 					if (!(scope instanceof SourceFileScope)) {
617 						if (!scope.getVariableDeclarations().isEmpty()) {
618 							for (VariableNameDeclaration variableNameDeclaration : scope.getVariableDeclarations()
619 									.keySet()) {
620 								DefaultMutableTreeNode variableNameDeclarationTreeNode = new DefaultMutableTreeNode(
621 										"Variable name declaration: " + variableNameDeclaration);
622 								scopeTreeNode.add(variableNameDeclarationTreeNode);
623 								for (NameOccurrence nameOccurrence : scope.getVariableDeclarations().get(
624 										variableNameDeclaration)) {
625 									DefaultMutableTreeNode nameOccurenceTreeNode = new DefaultMutableTreeNode(
626 											"Name occurrence: " + nameOccurrence);
627 									variableNameDeclarationTreeNode.add(nameOccurenceTreeNode);
628 								}
629 							}
630 						}
631 					}
632 				}
633 
634 				List<String> attributes = astTreeNode.getAttributes();
635 				DefaultMutableTreeNode attributesNode = new DefaultMutableTreeNode("Attributes (accessible via XPath):");
636 				selectedAstTreeNode.add(attributesNode);
637 				for (String attribute : attributes) {
638 					attributesNode.add(new DefaultMutableTreeNode(attribute));
639 				}
640 
641 				loadSymbolTableTreeData(symbolTableTreeNode);
642 			}
643 		}
644 	}
645 
646 	private class CodeHighlightListener implements TreeSelectionListener {
647 		public void valueChanged(TreeSelectionEvent e) {
648 			if (e.getNewLeadSelectionPath() != null) {
649 				ASTTreeNode selected = (ASTTreeNode) e.getNewLeadSelectionPath().getLastPathComponent();
650 				if (selected != null) {
651 					codeEditorPane.select(selected.node);
652 				}
653 			}
654 		}
655 	}
656 
657 	private class ASTListCellRenderer extends JLabel implements ListCellRenderer {
658 		private static final long serialVersionUID = 1L;
659 
660 		public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
661 				boolean cellHasFocus) {
662 
663 			if (isSelected) {
664 				setBackground(list.getSelectionBackground());
665 				setForeground(list.getSelectionForeground());
666 			} else {
667 				setBackground(list.getBackground());
668 				setForeground(list.getForeground());
669 			}
670 
671 			String text;
672 			if (value instanceof Node) {
673 				Node node = (Node) value;
674 				StringBuffer sb = new StringBuffer();
675 				String name = node.getClass().getName().substring(node.getClass().getName().lastIndexOf('.') + 1);
676 				if (Proxy.isProxyClass(value.getClass())) {
677 					name = value.toString();
678 				}
679 				sb.append(name).append(" at line ").append(node.getBeginLine()).append(" column ").append(
680 						node.getBeginColumn()).append(PMD.EOL);
681 				text = sb.toString();
682 			} else {
683 				text = value.toString();
684 			}
685 			setText(text);
686 			return this;
687 		}
688 	}
689 
690 	private class ASTSelectionListener implements ListSelectionListener {
691 		public void valueChanged(ListSelectionEvent e) {
692 			ListSelectionModel lsm = (ListSelectionModel) e.getSource();
693 			if (!lsm.isSelectionEmpty()) {
694 				Object o = xpathResults.get(lsm.getMinSelectionIndex());
695 				if (o instanceof Node) {
696 					codeEditorPane.select((Node) o);
697 				}
698 			}
699 		}
700 	}
701 
702 	private boolean exitOnClose = true;
703 	private final CodeEditorTextPane codeEditorPane = new CodeEditorTextPane();
704 	private final TreeWidget astTreeWidget = new TreeWidget(new Object[0]);
705 	private DefaultListModel xpathResults = new DefaultListModel();
706 	private final JList xpathResultList = new JList(xpathResults);
707 	private final JTextArea xpathQueryArea = new JTextArea(15, 30);
708 	private final ButtonGroup xpathVersionButtonGroup = new ButtonGroup();
709 	private final TreeWidget symbolTableTreeWidget = new TreeWidget(new Object[0]);
710 	private final JFrame frame = new JFrame("PMD Rule Designer (v " + PMD.VERSION + ')');
711 	private final DFAPanel dfaPanel = new DFAPanel();
712 	private final JRadioButtonMenuItem[] languageVersionMenuItems = new JRadioButtonMenuItem[getSupportedLanguageVersions().length];
713 
714 	public Designer(String[] args) {
715 		if (args.length > 0) {
716 			exitOnClose = !args[0].equals("-noexitonclose");
717 		}
718 
719 		Initializer.initialize();
720 
721 		xpathQueryArea.setFont(new Font("Verdana", Font.PLAIN, 16));
722 		JSplitPane controlSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, createCodeEditorPanel(),
723 				createXPathQueryPanel());
724 
725 		JSplitPane astAndSymbolTablePane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, createASTPanel(),
726 				createSymbolTableResultPanel());
727 
728 		JSplitPane resultsSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, astAndSymbolTablePane,
729 				createXPathResultPanel());
730 
731 		JTabbedPane tabbed = new JTabbedPane();
732 		tabbed.addTab("Abstract Syntax Tree / XPath / Symbol Table", resultsSplitPane);
733 		tabbed.addTab("Data Flow Analysis", dfaPanel);
734 		tabbed.setMnemonicAt(0, KeyEvent.VK_A);
735 		tabbed.setMnemonicAt(1, KeyEvent.VK_D);
736 
737 		JSplitPane containerSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, controlSplitPane, tabbed);
738 		containerSplitPane.setContinuousLayout(true);
739 
740 		JMenuBar menuBar = createMenuBar();
741 		frame.setJMenuBar(menuBar);
742 		frame.getContentPane().add(containerSplitPane);
743 		frame.setDefaultCloseOperation(exitOnClose ? JFrame.EXIT_ON_CLOSE : JFrame.DISPOSE_ON_CLOSE);
744 
745 		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
746 		int screenHeight = screenSize.height;
747 		int screenWidth = screenSize.width;
748 
749 		frame.pack();
750 		frame.setSize(screenWidth * 3 / 4, screenHeight * 3 / 4);
751 		frame.setLocation((screenWidth - frame.getWidth()) / 2, (screenHeight - frame.getHeight()) / 2);
752 		frame.setVisible(true);
753 		int horozontalMiddleLocation = controlSplitPane.getMaximumDividerLocation() * 3 / 5;
754 		controlSplitPane.setDividerLocation(horozontalMiddleLocation);
755 		containerSplitPane.setDividerLocation(containerSplitPane.getMaximumDividerLocation() / 2);
756 		astAndSymbolTablePane.setDividerLocation(astAndSymbolTablePane.getMaximumDividerLocation() / 3);
757 		resultsSplitPane.setDividerLocation(horozontalMiddleLocation);
758 
759 		loadSettings();
760 	}
761 
762 	private JMenuBar createMenuBar() {
763 		JMenuBar menuBar = new JMenuBar();
764 		JMenu menu = new JMenu("Language");
765 		ButtonGroup group = new ButtonGroup();
766 
767 		LanguageVersion[] languageVersions = getSupportedLanguageVersions();
768 		for (int i = 0; i < languageVersions.length; i++) {
769 			LanguageVersion languageVersion = languageVersions[i];
770 			JRadioButtonMenuItem button = new JRadioButtonMenuItem(languageVersion.getShortName());
771 			languageVersionMenuItems[i] = button;
772 			group.add(button);
773 			menu.add(button);
774 		}
775 		languageVersionMenuItems[DEFAULT_LANGUAGE_VERSION_SELECTION_INDEX].setSelected(true);
776 		menuBar.add(menu);
777 
778 		JMenu actionsMenu = new JMenu("Actions");
779 		JMenuItem copyXMLItem = new JMenuItem("Copy xml to clipboard");
780 		copyXMLItem.addActionListener(new ActionListener() {
781 			public void actionPerformed(ActionEvent e) {
782 				copyXmlToClipboard();
783 			}
784 		});
785 		actionsMenu.add(copyXMLItem);
786 		JMenuItem createRuleXMLItem = new JMenuItem("Create rule XML");
787 		createRuleXMLItem.addActionListener(new ActionListener() {
788 			public void actionPerformed(ActionEvent e) {
789 				createRuleXML();
790 			}
791 		});
792 		actionsMenu.add(createRuleXMLItem);
793 		menuBar.add(actionsMenu);
794 
795 		return menuBar;
796 	}
797 
798 	private void createRuleXML() {
799 		CreateXMLRulePanel rulePanel = new CreateXMLRulePanel(xpathQueryArea, codeEditorPane);
800 		JFrame xmlframe = new JFrame("Create XML Rule");
801 		xmlframe.setContentPane(rulePanel);
802 		xmlframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
803 		xmlframe.setSize(new Dimension(600, 700));
804 		xmlframe.addComponentListener(new java.awt.event.ComponentAdapter() {
805 			@Override
806 			public void componentResized(ComponentEvent e) {
807 				JFrame tmp = (JFrame) e.getSource();
808 				if (tmp.getWidth() < 600 || tmp.getHeight() < 700) {
809 					tmp.setSize(600, 700);
810 				}
811 			}
812 		});
813 		int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
814 		int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;
815 		xmlframe.pack();
816 		xmlframe.setLocation((screenWidth - xmlframe.getWidth()) / 2, (screenHeight - xmlframe.getHeight()) / 2);
817 		xmlframe.setVisible(true);
818 	}
819 
820 	private JComponent createCodeEditorPanel() {
821 		JPanel p = new JPanel();
822 		p.setLayout(new BorderLayout());
823 		codeEditorPane.setBorder(BorderFactory.createLineBorder(Color.black));
824 		makeTextComponentUndoable(codeEditorPane);
825 
826 		p.add(new JLabel("Source code:"), BorderLayout.NORTH);
827 		p.add(new JScrollPane(codeEditorPane), BorderLayout.CENTER);
828 
829 		return p;
830 	}
831 
832 	private JComponent createASTPanel() {
833 		astTreeWidget.setCellRenderer(createNoImageTreeCellRenderer());
834 		TreeSelectionModel model = astTreeWidget.getSelectionModel();
835 		model.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
836 		model.addTreeSelectionListener(new SymbolTableListener());
837 		model.addTreeSelectionListener(new CodeHighlightListener());
838 		return new JScrollPane(astTreeWidget);
839 	}
840 
841 	private JComponent createXPathResultPanel() {
842 		xpathResults.addElement("No XPath results yet, run an XPath Query first.");
843 		xpathResultList.setBorder(BorderFactory.createLineBorder(Color.black));
844 		xpathResultList.setFixedCellWidth(300);
845 		xpathResultList.setCellRenderer(new ASTListCellRenderer());
846 		xpathResultList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
847 		xpathResultList.getSelectionModel().addListSelectionListener(new ASTSelectionListener());
848 		JScrollPane scrollPane = new JScrollPane();
849 		scrollPane.getViewport().setView(xpathResultList);
850 		return scrollPane;
851 	}
852 
853 	private JPanel createXPathQueryPanel() {
854 		JPanel p = new JPanel();
855 		p.setLayout(new BorderLayout());
856 		xpathQueryArea.setBorder(BorderFactory.createLineBorder(Color.black));
857 		makeTextComponentUndoable(xpathQueryArea);
858 		JScrollPane scrollPane = new JScrollPane(xpathQueryArea);
859 		scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
860 		scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
861 		final JButton b = createGoButton();
862 
863 		JPanel topPanel = new JPanel();
864 		topPanel.setLayout(new BorderLayout());
865 		topPanel.add(new JLabel("XPath Query (if any):"), BorderLayout.WEST);
866 		topPanel.add(createXPathVersionPanel(), BorderLayout.EAST);
867 
868 		p.add(topPanel, BorderLayout.NORTH);
869 		p.add(scrollPane, BorderLayout.CENTER);
870 		p.add(b, BorderLayout.SOUTH);
871 
872 		return p;
873 	}
874 
875 	private JComponent createSymbolTableResultPanel() {
876 		symbolTableTreeWidget.setCellRenderer(createNoImageTreeCellRenderer());
877 		return new JScrollPane(symbolTableTreeWidget);
878 	}
879 
880 	private JPanel createXPathVersionPanel() {
881 		JPanel p = new JPanel();
882 		p.add(new JLabel("XPath Version:"));
883 		for (Object[] values : XPathRule.VERSION_DESCRIPTOR.choices()) {
884 			JRadioButton b = new JRadioButton();
885 			b.setText((String) values[0]);
886 			b.setActionCommand(b.getText());
887 			if (values[0].equals(XPathRule.VERSION_DESCRIPTOR.defaultValue())) {
888 				b.setSelected(true);
889 			}
890 			xpathVersionButtonGroup.add(b);
891 			p.add(b);
892 		}
893 		return p;
894 	}
895 
896 	private JButton createGoButton() {
897 		JButton b = new JButton("Go");
898 		b.setMnemonic('g');
899 		b.addActionListener(new ShowListener());
900 		b.addActionListener(new XPathListener());
901 		b.addActionListener(new DFAListener());
902 		b.addActionListener(new ActionListener() {
903 			public void actionPerformed(ActionEvent e) {
904 				saveSettings();
905 			}
906 		});
907 		return b;
908 	}
909 
910 	private static void makeTextComponentUndoable(JTextComponent textConponent) {
911 		final UndoManager undoManager = new UndoManager();
912 		textConponent.getDocument().addUndoableEditListener(new UndoableEditListener() {
913 			public void undoableEditHappened(UndoableEditEvent evt) {
914 				undoManager.addEdit(evt.getEdit());
915 			}
916 		});
917 		ActionMap actionMap = textConponent.getActionMap();
918 		InputMap inputMap = textConponent.getInputMap();
919 		actionMap.put("Undo", new AbstractAction("Undo") {
920 			public void actionPerformed(ActionEvent evt) {
921 				try {
922 					if (undoManager.canUndo()) {
923 						undoManager.undo();
924 					}
925 				} catch (CannotUndoException e) {
926 				}
927 			}
928 		});
929 		inputMap.put(KeyStroke.getKeyStroke("control Z"), "Undo");
930 
931 		actionMap.put("Redo", new AbstractAction("Redo") {
932 			public void actionPerformed(ActionEvent evt) {
933 				try {
934 					if (undoManager.canRedo()) {
935 						undoManager.redo();
936 					}
937 				} catch (CannotRedoException e) {
938 				}
939 			}
940 		});
941 		inputMap.put(KeyStroke.getKeyStroke("control Y"), "Redo");
942 	}
943 
944 	public static void main(String[] args) {
945 		new Designer(args);
946 	}
947 
948 	private final void copyXmlToClipboard() {
949 		if (codeEditorPane.getText() != null && codeEditorPane.getText().trim().length() > 0) {
950 			String xml = "";
951 			Node cu = getCompilationUnit();
952 			if (cu != null) {
953 				try {
954 					xml = getXmlString(cu);
955 				} catch (TransformerException e) {
956 					e.printStackTrace();
957 					xml = "Error trying to construct XML representation";
958 				}
959 			}
960 			Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(xml), this);
961 		}
962 	}
963 
964 	/**
965 	 * Returns an unformatted xml string (without the declaration)
966 	 *
967 	 * @throws TransformerException if the XML cannot be converted to a string
968 	 */
969 	private String getXmlString(Node node) throws TransformerException {
970 		StringWriter writer = new StringWriter();
971 
972 		Source source = new DOMSource(node.getAsDocument());
973 		Result result = new StreamResult(writer);
974 		TransformerFactory transformerFactory = TransformerFactory.newInstance();
975 		transformerFactory.setAttribute("indent-number", 3);
976 		Transformer xformer = transformerFactory.newTransformer();
977 		xformer.setOutputProperty(OutputKeys.INDENT, "yes");
978 		xformer.transform(source, result);
979 
980 		return writer.toString();
981 	}
982 
983 	public void lostOwnership(Clipboard clipboard, Transferable contents) {
984 	}
985 
986 	private static final String SETTINGS_FILE_NAME = System.getProperty("user.home")
987 	+ System.getProperty("file.separator") + ".pmd_designer.xml";
988 
989 	private void loadSettings() {
990 		try {
991 			File file = new File(SETTINGS_FILE_NAME);
992 			if (file.exists()) {
993 				DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
994 				Document document = builder.parse(new FileInputStream(file));
995 				Element settingsElement = document.getDocumentElement();
996 				Element codeElement = (Element) settingsElement.getElementsByTagName("code").item(0);
997 				Element xpathElement = (Element) settingsElement.getElementsByTagName("xpath").item(0);
998 
999 				String code = getTextContext(codeElement);
1000 				String languageVersion = codeElement.getAttribute("language-version");
1001 				String xpath = getTextContext(xpathElement);
1002 				String xpathVersion = xpathElement.getAttribute("version");
1003 
1004 				codeEditorPane.setText(code);
1005 				setLanguageVersion(LanguageVersion.findByTerseName(languageVersion));
1006 				xpathQueryArea.setText(xpath);
1007 				for (Enumeration<AbstractButton> e = xpathVersionButtonGroup.getElements(); e.hasMoreElements();) {
1008 					AbstractButton button = e.nextElement();
1009 					if (xpathVersion.equals(button.getActionCommand())) {
1010 						button.setSelected(true);
1011 						break;
1012 					}
1013 				}
1014 			}
1015 		} catch (ParserConfigurationException e) {
1016 			e.printStackTrace();
1017 		} catch (IOException e) {
1018 			e.printStackTrace();
1019 		} catch (SAXException e) {
1020 			e.printStackTrace();
1021 		}
1022 	}
1023 
1024 	private void saveSettings() {
1025 		try {
1026 			DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
1027 			DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
1028 			Document document = documentBuilder.newDocument();
1029 
1030 			Element settingsElement = document.createElement("settings");
1031 			document.appendChild(settingsElement);
1032 
1033 			Element codeElement = document.createElement("code");
1034 			settingsElement.appendChild(codeElement);
1035 			codeElement.setAttribute("language-version", getLanguageVersion().getTerseName());
1036 			codeElement.appendChild(document.createCDATASection(codeEditorPane.getText()));
1037 
1038 			Element xpathElement = document.createElement("xpath");
1039 			settingsElement.appendChild(xpathElement);
1040 			xpathElement.setAttribute("version", xpathVersionButtonGroup.getSelection().getActionCommand());
1041 			xpathElement.appendChild(document.createCDATASection(xpathQueryArea.getText()));
1042 
1043 			TransformerFactory transformerFactory = TransformerFactory.newInstance();
1044 			Transformer transformer = transformerFactory.newTransformer();
1045 			transformer.setOutputProperty(OutputKeys.METHOD, "xml");
1046 			// This is as close to pretty printing as we'll get using standard Java APIs.
1047 			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
1048 			transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3");
1049 			transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
1050 
1051 			Source source = new DOMSource(document);
1052 			Result result = new StreamResult(new FileWriter(new File(SETTINGS_FILE_NAME)));
1053 			transformer.transform(source, result);
1054 		} catch (ParserConfigurationException e) {
1055 			e.printStackTrace();
1056 		} catch (IOException e) {
1057 			e.printStackTrace();
1058 		} catch (TransformerException e) {
1059 			e.printStackTrace();
1060 		}
1061 	}
1062 
1063 	private String getTextContext(Element element) {
1064 		StringBuilder buf = new StringBuilder();
1065 		for (int i = 0; i < element.getChildNodes().getLength(); i++) {
1066 			org.w3c.dom.Node child = element.getChildNodes().item(i);
1067 			if (child instanceof Text) {
1068 				buf.append(((Text)child).getData());
1069 			}
1070 		}
1071 		return buf.toString();
1072 	}
1073 }