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