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