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 }