0001 /**
0002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
0003 */
0004 package net.sourceforge.pmd.util.designer;
0005
0006 import java.awt.BorderLayout;
0007 import java.awt.Color;
0008 import java.awt.Component;
0009 import java.awt.Dimension;
0010 import java.awt.Font;
0011 import java.awt.Toolkit;
0012 import java.awt.datatransfer.Clipboard;
0013 import java.awt.datatransfer.ClipboardOwner;
0014 import java.awt.datatransfer.StringSelection;
0015 import java.awt.datatransfer.Transferable;
0016 import java.awt.event.ActionEvent;
0017 import java.awt.event.ActionListener;
0018 import java.awt.event.ComponentEvent;
0019 import java.awt.event.KeyEvent;
0020 import java.awt.event.MouseEvent;
0021 import java.io.File;
0022 import java.io.FileInputStream;
0023 import java.io.FileWriter;
0024 import java.io.IOException;
0025 import java.io.StringReader;
0026 import java.io.StringWriter;
0027 import java.lang.reflect.Proxy;
0028 import java.util.ArrayList;
0029 import java.util.Arrays;
0030 import java.util.Collections;
0031 import java.util.Enumeration;
0032 import java.util.List;
0033
0034 import javax.swing.AbstractAction;
0035 import javax.swing.AbstractButton;
0036 import javax.swing.ActionMap;
0037 import javax.swing.BorderFactory;
0038 import javax.swing.ButtonGroup;
0039 import javax.swing.DefaultListModel;
0040 import javax.swing.InputMap;
0041 import javax.swing.JButton;
0042 import javax.swing.JComponent;
0043 import javax.swing.JFrame;
0044 import javax.swing.JLabel;
0045 import javax.swing.JList;
0046 import javax.swing.JMenu;
0047 import javax.swing.JMenuBar;
0048 import javax.swing.JMenuItem;
0049 import javax.swing.JPanel;
0050 import javax.swing.JRadioButton;
0051 import javax.swing.JRadioButtonMenuItem;
0052 import javax.swing.JScrollPane;
0053 import javax.swing.JSplitPane;
0054 import javax.swing.JTabbedPane;
0055 import javax.swing.JTextArea;
0056 import javax.swing.JTree;
0057 import javax.swing.KeyStroke;
0058 import javax.swing.ListCellRenderer;
0059 import javax.swing.ListSelectionModel;
0060 import javax.swing.ScrollPaneConstants;
0061 import javax.swing.WindowConstants;
0062 import javax.swing.event.ListSelectionEvent;
0063 import javax.swing.event.ListSelectionListener;
0064 import javax.swing.event.TreeSelectionEvent;
0065 import javax.swing.event.TreeSelectionListener;
0066 import javax.swing.event.UndoableEditEvent;
0067 import javax.swing.event.UndoableEditListener;
0068 import javax.swing.text.JTextComponent;
0069 import javax.swing.tree.DefaultMutableTreeNode;
0070 import javax.swing.tree.DefaultTreeCellRenderer;
0071 import javax.swing.tree.DefaultTreeModel;
0072 import javax.swing.tree.TreeCellRenderer;
0073 import javax.swing.tree.TreeNode;
0074 import javax.swing.tree.TreePath;
0075 import javax.swing.tree.TreeSelectionModel;
0076 import javax.swing.undo.CannotRedoException;
0077 import javax.swing.undo.CannotUndoException;
0078 import javax.swing.undo.UndoManager;
0079 import javax.xml.parsers.DocumentBuilder;
0080 import javax.xml.parsers.DocumentBuilderFactory;
0081 import javax.xml.parsers.ParserConfigurationException;
0082 import javax.xml.transform.OutputKeys;
0083 import javax.xml.transform.Result;
0084 import javax.xml.transform.Source;
0085 import javax.xml.transform.Transformer;
0086 import javax.xml.transform.TransformerException;
0087 import javax.xml.transform.TransformerFactory;
0088 import javax.xml.transform.dom.DOMSource;
0089 import javax.xml.transform.stream.StreamResult;
0090
0091 import net.sourceforge.pmd.PMD;
0092 import net.sourceforge.pmd.RuleContext;
0093 import net.sourceforge.pmd.RuleSet;
0094 import net.sourceforge.pmd.RuleSets;
0095 import net.sourceforge.pmd.lang.Language;
0096 import net.sourceforge.pmd.lang.LanguageVersion;
0097 import net.sourceforge.pmd.lang.LanguageVersionHandler;
0098 import net.sourceforge.pmd.lang.Parser;
0099 import net.sourceforge.pmd.lang.ast.Node;
0100 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
0101 import net.sourceforge.pmd.lang.java.ast.AccessNode;
0102 import net.sourceforge.pmd.lang.java.ast.JavaNode;
0103 import net.sourceforge.pmd.lang.java.ast.ParseException;
0104 import net.sourceforge.pmd.lang.java.symboltable.ClassNameDeclaration;
0105 import net.sourceforge.pmd.lang.java.symboltable.ClassScope;
0106 import net.sourceforge.pmd.lang.java.symboltable.LocalScope;
0107 import net.sourceforge.pmd.lang.java.symboltable.MethodNameDeclaration;
0108 import net.sourceforge.pmd.lang.java.symboltable.MethodScope;
0109 import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
0110 import net.sourceforge.pmd.lang.java.symboltable.Scope;
0111 import net.sourceforge.pmd.lang.java.symboltable.SourceFileScope;
0112 import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
0113 import net.sourceforge.pmd.lang.rule.XPathRule;
0114 import net.sourceforge.pmd.lang.xpath.Initializer;
0115 import net.sourceforge.pmd.util.StringUtil;
0116
0117 import org.w3c.dom.Document;
0118 import org.w3c.dom.Element;
0119 import org.w3c.dom.Text;
0120 import org.xml.sax.SAXException;
0121
0122 public class Designer implements ClipboardOwner {
0123
0124 private static final int DEFAULT_LANGUAGE_VERSION_SELECTION_INDEX = Arrays.asList(getSupportedLanguageVersions())
0125 .indexOf(Language.JAVA.getDefaultVersion());
0126
0127 private Node getCompilationUnit() {
0128 LanguageVersionHandler languageVersionHandler = getLanguageVersionHandler();
0129 Parser parser = languageVersionHandler.getParser();
0130 Node node = parser.parse(null, new StringReader(codeEditorPane.getText()));
0131 languageVersionHandler.getSymbolFacade().start(node);
0132 languageVersionHandler.getTypeResolutionFacade(null).start(node);
0133 return node;
0134 }
0135
0136 private static LanguageVersion[] getSupportedLanguageVersions() {
0137 List<LanguageVersion> languageVersions = new ArrayList<LanguageVersion>();
0138 for (LanguageVersion languageVersion : LanguageVersion.values()) {
0139 if (languageVersion.getLanguageVersionHandler() != null) {
0140 Parser parser = languageVersion.getLanguageVersionHandler().getParser();
0141 if (parser != null && parser.canParse()) {
0142 languageVersions.add(languageVersion);
0143 }
0144 }
0145 }
0146 return languageVersions.toArray(new LanguageVersion[languageVersions.size()]);
0147 }
0148
0149 private LanguageVersion getLanguageVersion() {
0150 return getSupportedLanguageVersions()[selectedLanguageVersionIndex()];
0151 }
0152
0153 private void setLanguageVersion(LanguageVersion languageVersion) {
0154 if (languageVersion != null) {
0155 LanguageVersion[] versions = getSupportedLanguageVersions();
0156 for (int i = 0; i < versions.length; i++) {
0157 LanguageVersion version = versions[i];
0158 if (languageVersion.equals(version)) {
0159 languageVersionMenuItems[i].setSelected(true);
0160 break;
0161 }
0162 }
0163 }
0164 }
0165
0166 private int selectedLanguageVersionIndex() {
0167 for (int i = 0; i < languageVersionMenuItems.length; i++) {
0168 if (languageVersionMenuItems[i].isSelected()) {
0169 return i;
0170 }
0171 }
0172 throw new RuntimeException("Initial default language version not specified");
0173 }
0174
0175 private LanguageVersionHandler getLanguageVersionHandler() {
0176 LanguageVersion languageVersion = getLanguageVersion();
0177 return languageVersion.getLanguageVersionHandler();
0178 }
0179
0180 private class ExceptionNode implements TreeNode {
0181
0182 private Object item;
0183 private ExceptionNode[] kids;
0184
0185 public ExceptionNode(Object theItem) {
0186 item = theItem;
0187
0188 if (item instanceof ParseException) {
0189 createKids();
0190 }
0191 }
0192
0193 // each line in the error message becomes a separate tree node
0194 private void createKids() {
0195
0196 String message = ((ParseException) item).getMessage();
0197 String[] lines = StringUtil.substringsOf(message, PMD.EOL);
0198
0199 kids = new ExceptionNode[lines.length];
0200 for (int i = 0; i < lines.length; i++) {
0201 kids[i] = new ExceptionNode(lines[i]);
0202 }
0203 }
0204
0205 public int getChildCount() {
0206 return kids == null ? 0 : kids.length;
0207 }
0208
0209 public boolean getAllowsChildren() {
0210 return false;
0211 }
0212
0213 public boolean isLeaf() {
0214 return kids == null;
0215 }
0216
0217 public TreeNode getParent() {
0218 return null;
0219 }
0220
0221 public TreeNode getChildAt(int childIndex) {
0222 return kids[childIndex];
0223 }
0224
0225 public String label() {
0226 return item.toString();
0227 }
0228
0229 public Enumeration<ExceptionNode> children() {
0230 Enumeration<ExceptionNode> e = new Enumeration<ExceptionNode>() {
0231 int i = 0;
0232
0233 public boolean hasMoreElements() {
0234 return kids != null && i < kids.length;
0235 }
0236
0237 public ExceptionNode nextElement() {
0238 return kids[i++];
0239 }
0240 };
0241 return e;
0242 }
0243
0244 public int getIndex(TreeNode node) {
0245 for (int i = 0; i < kids.length; i++) {
0246 if (kids[i] == node) {
0247 return i;
0248 }
0249 }
0250 return -1;
0251 }
0252 }
0253
0254 // Tree node that wraps the AST node for the tree widget and
0255 // any possible children they may have.
0256 private class ASTTreeNode implements TreeNode {
0257
0258 private Node node;
0259 private ASTTreeNode parent;
0260 private ASTTreeNode[] kids;
0261
0262 public ASTTreeNode(Node theNode) {
0263 node = theNode;
0264
0265 Node parent = node.jjtGetParent();
0266 if (parent != null) {
0267 this.parent = new ASTTreeNode(parent);
0268 }
0269 }
0270
0271 private ASTTreeNode(ASTTreeNode parent, Node theNode) {
0272 node = theNode;
0273 this.parent = parent;
0274 }
0275
0276 public int getChildCount() {
0277 return node.jjtGetNumChildren();
0278 }
0279
0280 public boolean getAllowsChildren() {
0281 return false;
0282 }
0283
0284 public boolean isLeaf() {
0285 return node.jjtGetNumChildren() == 0;
0286 }
0287
0288 public TreeNode getParent() {
0289 return parent;
0290 }
0291
0292 public Scope getScope() {
0293 if (node instanceof JavaNode) {
0294 return ((JavaNode) node).getScope();
0295 }
0296 return null;
0297 }
0298
0299 public Enumeration<ASTTreeNode> children() {
0300
0301 if (getChildCount() > 0) {
0302 getChildAt(0); // force it to build kids
0303 }
0304
0305 Enumeration<ASTTreeNode> e = new Enumeration<ASTTreeNode>() {
0306 int i = 0;
0307
0308 public boolean hasMoreElements() {
0309 return kids != null && i < kids.length;
0310 }
0311
0312 public ASTTreeNode nextElement() {
0313 return kids[i++];
0314 }
0315 };
0316 return e;
0317 }
0318
0319 public TreeNode getChildAt(int childIndex) {
0320
0321 if (kids == null) {
0322 kids = new ASTTreeNode[node.jjtGetNumChildren()];
0323 for (int i = 0; i < kids.length; i++) {
0324 kids[i] = new ASTTreeNode(this.parent, node.jjtGetChild(i));
0325 }
0326 }
0327 return kids[childIndex];
0328 }
0329
0330 public int getIndex(TreeNode node) {
0331
0332 for (int i = 0; i < kids.length; i++) {
0333 if (kids[i] == node) {
0334 return i;
0335 }
0336 }
0337 return -1;
0338 }
0339
0340 public String label() {
0341 LanguageVersionHandler languageVersionHandler = getLanguageVersionHandler();
0342 StringWriter writer = new StringWriter();
0343 languageVersionHandler.getDumpFacade(writer, "", false).start(node);
0344 return writer.toString();
0345 }
0346
0347 public String getToolTipText() {
0348 String tooltip = "Line: " + node.getBeginLine() + " Column: " + node.getBeginColumn();
0349
0350 if (node instanceof AccessNode) {
0351 AccessNode accessNode = (AccessNode) node;
0352 if (!"".equals(tooltip)) {
0353 tooltip += ",";
0354 }
0355 tooltip += accessNode.isAbstract() ? " Abstract" : "";
0356 tooltip += accessNode.isStatic() ? " Static" : "";
0357 tooltip += accessNode.isFinal() ? " Final" : "";
0358 tooltip += accessNode.isNative() ? " Native" : "";
0359 tooltip += accessNode.isPrivate() ? " Private" : "";
0360 tooltip += accessNode.isSynchronized() ? " Synchronised" : "";
0361 tooltip += accessNode.isTransient() ? " Transient" : "";
0362 tooltip += accessNode.isVolatile() ? " Volatile" : "";
0363 tooltip += accessNode.isStrictfp() ? " Strictfp" : "";
0364 }
0365 return tooltip;
0366 }
0367 }
0368
0369 private TreeCellRenderer createNoImageTreeCellRenderer() {
0370 DefaultTreeCellRenderer treeCellRenderer = new DefaultTreeCellRenderer();
0371 treeCellRenderer.setLeafIcon(null);
0372 treeCellRenderer.setOpenIcon(null);
0373 treeCellRenderer.setClosedIcon(null);
0374 return treeCellRenderer;
0375 }
0376
0377 // Special tree variant that knows how to retrieve node labels and
0378 // provides the ability to expand all nodes at once.
0379 private class TreeWidget extends JTree {
0380
0381 private static final long serialVersionUID = 1L;
0382
0383 public TreeWidget(Object[] items) {
0384 super(items);
0385 setToolTipText("");
0386 }
0387
0388 @Override
0389 public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row,
0390 boolean hasFocus) {
0391 if (value == null) {
0392 return "";
0393 }
0394 if (value instanceof ASTTreeNode) {
0395 return ((ASTTreeNode) value).label();
0396 }
0397 if (value instanceof ExceptionNode) {
0398 return ((ExceptionNode) value).label();
0399 }
0400 return value.toString();
0401 }
0402
0403 @Override
0404 public String getToolTipText(MouseEvent e) {
0405 if (getRowForLocation(e.getX(), e.getY()) == -1) {
0406 return null;
0407 }
0408 TreePath curPath = getPathForLocation(e.getX(), e.getY());
0409 if (curPath.getLastPathComponent() instanceof ASTTreeNode) {
0410 return ((ASTTreeNode) curPath.getLastPathComponent()).getToolTipText();
0411 } else {
0412 return super.getToolTipText(e);
0413 }
0414 }
0415
0416 public void expandAll(boolean expand) {
0417 TreeNode root = (TreeNode) getModel().getRoot();
0418 expandAll(new TreePath(root), expand);
0419 }
0420
0421 private void expandAll(TreePath parent, boolean expand) {
0422 // Traverse children
0423 TreeNode node = (TreeNode) parent.getLastPathComponent();
0424 if (node.getChildCount() >= 0) {
0425 for (Enumeration<TreeNode> e = node.children(); e.hasMoreElements();) {
0426 TreeNode n = e.nextElement();
0427 TreePath path = parent.pathByAddingChild(n);
0428 expandAll(path, expand);
0429 }
0430 }
0431
0432 if (expand) {
0433 expandPath(parent);
0434 } else {
0435 collapsePath(parent);
0436 }
0437 }
0438 }
0439
0440 private void loadASTTreeData(TreeNode rootNode) {
0441 astTreeWidget.setModel(new DefaultTreeModel(rootNode));
0442 astTreeWidget.setRootVisible(true);
0443 astTreeWidget.expandAll(true);
0444 }
0445
0446 private void loadSymbolTableTreeData(TreeNode rootNode) {
0447 if (rootNode != null) {
0448 symbolTableTreeWidget.setModel(new DefaultTreeModel(rootNode));
0449 symbolTableTreeWidget.expandAll(true);
0450 } else {
0451 symbolTableTreeWidget.setModel(null);
0452 }
0453 }
0454
0455 private class ShowListener implements ActionListener {
0456 public void actionPerformed(ActionEvent ae) {
0457 TreeNode tn;
0458 try {
0459 Node lastCompilationUnit = getCompilationUnit();
0460 tn = new ASTTreeNode(lastCompilationUnit);
0461 } catch (ParseException pe) {
0462 tn = new ExceptionNode(pe);
0463 }
0464
0465 loadASTTreeData(tn);
0466 loadSymbolTableTreeData(null);
0467 }
0468 }
0469
0470 private class DFAListener implements ActionListener {
0471 public void actionPerformed(ActionEvent ae) {
0472
0473 DFAGraphRule dfaGraphRule = new DFAGraphRule();
0474 RuleSet rs = new RuleSet();
0475 LanguageVersion languageVersion = getLanguageVersion();
0476 if (languageVersion.getLanguage().equals(Language.JAVA)) {
0477 rs.addRule(dfaGraphRule);
0478 }
0479 RuleContext ctx = new RuleContext();
0480 ctx.setSourceCodeFilename("[no filename]." + languageVersion.getLanguage().getExtensions().get(0));
0481 StringReader reader = new StringReader(codeEditorPane.getText());
0482 PMD pmd = new PMD();
0483 pmd.getConfiguration().setDefaultLanguageVersion(languageVersion);
0484
0485 try {
0486 pmd.processFile(reader, new RuleSets(rs), ctx);
0487 // } catch (PMDException pmde) {
0488 // loadTreeData(new ExceptionNode(pmde));
0489 } catch (Exception e) {
0490 e.printStackTrace();
0491 }
0492
0493 List<ASTMethodDeclaration> methods = dfaGraphRule.getMethods();
0494 if (methods != null && !methods.isEmpty()) {
0495 dfaPanel.resetTo(methods, codeEditorPane);
0496 dfaPanel.repaint();
0497 }
0498 }
0499 }
0500
0501 private class XPathListener implements ActionListener {
0502 public void actionPerformed(ActionEvent ae) {
0503 xpathResults.clear();
0504 if (xpathQueryArea.getText().length() == 0) {
0505 xpathResults.addElement("XPath query field is empty.");
0506 xpathResultList.repaint();
0507 codeEditorPane.requestFocus();
0508 return;
0509 }
0510 Node c = getCompilationUnit();
0511 try {
0512 XPathRule xpathRule = new XPathRule() {
0513 @Override
0514 public void addViolation(Object data, Node node, String arg) {
0515 xpathResults.addElement(node);
0516 }
0517 };
0518 xpathRule.setMessage("");
0519 xpathRule.setLanguage(getLanguageVersion().getLanguage());
0520 xpathRule.setProperty(XPathRule.XPATH_DESCRIPTOR, xpathQueryArea.getText());
0521 xpathRule.setProperty(XPathRule.VERSION_DESCRIPTOR, xpathVersionButtonGroup.getSelection()
0522 .getActionCommand());
0523
0524 RuleSet ruleSet = new RuleSet();
0525 ruleSet.addRule(xpathRule);
0526
0527 RuleSets ruleSets = new RuleSets();
0528 ruleSets.addRuleSet(ruleSet);
0529
0530 RuleContext ruleContext = new RuleContext();
0531 ruleContext.setLanguageVersion(getLanguageVersion());
0532
0533 List<Node> nodes = new ArrayList<Node>();
0534 nodes.add(c);
0535 ruleSets.apply(nodes, ruleContext, xpathRule.getLanguage());
0536
0537 if (xpathResults.isEmpty()) {
0538 xpathResults.addElement("No matching nodes " + System.currentTimeMillis());
0539 }
0540 } catch (ParseException pe) {
0541 xpathResults.addElement(pe.fillInStackTrace().getMessage());
0542 }
0543 xpathResultList.repaint();
0544 xpathQueryArea.requestFocus();
0545 }
0546 }
0547
0548 private class SymbolTableListener implements TreeSelectionListener {
0549 public void valueChanged(TreeSelectionEvent e) {
0550 if (e.getNewLeadSelectionPath() != null) {
0551 ASTTreeNode astTreeNode = (ASTTreeNode) e.getNewLeadSelectionPath().getLastPathComponent();
0552
0553 DefaultMutableTreeNode symbolTableTreeNode = new DefaultMutableTreeNode();
0554 DefaultMutableTreeNode selectedAstTreeNode = new DefaultMutableTreeNode("AST Node: "
0555 + astTreeNode.label());
0556 symbolTableTreeNode.add(selectedAstTreeNode);
0557
0558 List<Scope> scopes = new ArrayList<Scope>();
0559 Scope scope = astTreeNode.getScope();
0560 while (scope != null) {
0561 scopes.add(scope);
0562 scope = scope.getParent();
0563 }
0564 Collections.reverse(scopes);
0565 for (int i = 0; i < scopes.size(); i++) {
0566 scope = scopes.get(i);
0567 DefaultMutableTreeNode scopeTreeNode = new DefaultMutableTreeNode("Scope: "
0568 + scope.getClass().getSimpleName());
0569 selectedAstTreeNode.add(scopeTreeNode);
0570 if (!(scope instanceof MethodScope || scope instanceof LocalScope)) {
0571 if (!scope.getClassDeclarations().isEmpty()) {
0572 for (ClassNameDeclaration classNameDeclaration : scope.getClassDeclarations().keySet()) {
0573 DefaultMutableTreeNode classNameDeclarationTreeNode = new DefaultMutableTreeNode(
0574 "Class name declaration: " + classNameDeclaration);
0575 scopeTreeNode.add(classNameDeclarationTreeNode);
0576 for (NameOccurrence nameOccurrence : scope.getClassDeclarations().get(
0577 classNameDeclaration)) {
0578 DefaultMutableTreeNode nameOccurenceTreeNode = new DefaultMutableTreeNode(
0579 "Name occurrence: " + nameOccurrence);
0580 classNameDeclarationTreeNode.add(nameOccurenceTreeNode);
0581 }
0582 }
0583 }
0584 }
0585 if (scope instanceof ClassScope) {
0586 ClassScope classScope = (ClassScope) scope;
0587 if (!classScope.getMethodDeclarations().isEmpty()) {
0588 for (MethodNameDeclaration methodNameDeclaration : classScope.getMethodDeclarations()
0589 .keySet()) {
0590 DefaultMutableTreeNode methodNameDeclarationTreeNode = new DefaultMutableTreeNode(
0591 "Method name declaration: " + methodNameDeclaration);
0592 scopeTreeNode.add(methodNameDeclarationTreeNode);
0593 for (NameOccurrence nameOccurrence : classScope.getMethodDeclarations().get(
0594 methodNameDeclaration)) {
0595 DefaultMutableTreeNode nameOccurenceTreeNode = new DefaultMutableTreeNode(
0596 "Name occurrence: " + nameOccurrence);
0597 methodNameDeclarationTreeNode.add(nameOccurenceTreeNode);
0598 }
0599 }
0600 }
0601 }
0602 if (!(scope instanceof SourceFileScope)) {
0603 if (!scope.getVariableDeclarations().isEmpty()) {
0604 for (VariableNameDeclaration variableNameDeclaration : scope.getVariableDeclarations()
0605 .keySet()) {
0606 DefaultMutableTreeNode variableNameDeclarationTreeNode = new DefaultMutableTreeNode(
0607 "Variable name declaration: " + variableNameDeclaration);
0608 scopeTreeNode.add(variableNameDeclarationTreeNode);
0609 for (NameOccurrence nameOccurrence : scope.getVariableDeclarations().get(
0610 variableNameDeclaration)) {
0611 DefaultMutableTreeNode nameOccurenceTreeNode = new DefaultMutableTreeNode(
0612 "Name occurrence: " + nameOccurrence);
0613 variableNameDeclarationTreeNode.add(nameOccurenceTreeNode);
0614 }
0615 }
0616 }
0617 }
0618 }
0619 loadSymbolTableTreeData(symbolTableTreeNode);
0620 }
0621 }
0622 }
0623
0624 private class CodeHighlightListener implements TreeSelectionListener {
0625 public void valueChanged(TreeSelectionEvent e) {
0626 if (e.getNewLeadSelectionPath() != null) {
0627 ASTTreeNode selected = (ASTTreeNode) e.getNewLeadSelectionPath().getLastPathComponent();
0628 if (selected != null) {
0629 codeEditorPane.select(selected.node);
0630 }
0631 }
0632 }
0633 }
0634
0635 private class ASTListCellRenderer extends JLabel implements ListCellRenderer {
0636 private static final long serialVersionUID = 1L;
0637
0638 public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
0639 boolean cellHasFocus) {
0640
0641 if (isSelected) {
0642 setBackground(list.getSelectionBackground());
0643 setForeground(list.getSelectionForeground());
0644 } else {
0645 setBackground(list.getBackground());
0646 setForeground(list.getForeground());
0647 }
0648
0649 String text;
0650 if (value instanceof Node) {
0651 Node node = (Node) value;
0652 StringBuffer sb = new StringBuffer();
0653 String name = node.getClass().getName().substring(node.getClass().getName().lastIndexOf('.') + 1);
0654 if (Proxy.isProxyClass(value.getClass())) {
0655 name = value.toString();
0656 }
0657 sb.append(name).append(" at line ").append(node.getBeginLine()).append(" column ").append(
0658 node.getBeginColumn()).append(PMD.EOL);
0659 text = sb.toString();
0660 } else {
0661 text = value.toString();
0662 }
0663 setText(text);
0664 return this;
0665 }
0666 }
0667
0668 private class ASTSelectionListener implements ListSelectionListener {
0669 public void valueChanged(ListSelectionEvent e) {
0670 ListSelectionModel lsm = (ListSelectionModel) e.getSource();
0671 if (!lsm.isSelectionEmpty()) {
0672 Object o = xpathResults.get(lsm.getMinSelectionIndex());
0673 if (o instanceof Node) {
0674 codeEditorPane.select((Node) o);
0675 }
0676 }
0677 }
0678 }
0679
0680 private boolean exitOnClose = true;
0681 private final CodeEditorTextPane codeEditorPane = new CodeEditorTextPane();
0682 private final TreeWidget astTreeWidget = new TreeWidget(new Object[0]);
0683 private DefaultListModel xpathResults = new DefaultListModel();
0684 private final JList xpathResultList = new JList(xpathResults);
0685 private final JTextArea xpathQueryArea = new JTextArea(15, 30);
0686 private final ButtonGroup xpathVersionButtonGroup = new ButtonGroup();
0687 private final TreeWidget symbolTableTreeWidget = new TreeWidget(new Object[0]);
0688 private final JFrame frame = new JFrame("PMD Rule Designer (v " + PMD.VERSION + ')');
0689 private final DFAPanel dfaPanel = new DFAPanel();
0690 private final JRadioButtonMenuItem[] languageVersionMenuItems = new JRadioButtonMenuItem[getSupportedLanguageVersions().length];
0691
0692 public Designer(String[] args) {
0693 if (args.length > 0) {
0694 exitOnClose = !args[0].equals("-noexitonclose");
0695 }
0696
0697 Initializer.initialize();
0698
0699 xpathQueryArea.setFont(new Font("Verdana", Font.PLAIN, 16));
0700 JSplitPane controlSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, createCodeEditorPanel(),
0701 createXPathQueryPanel());
0702
0703 JSplitPane astAndSymbolTablePane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, createASTPanel(),
0704 createSymbolTableResultPanel());
0705
0706 JSplitPane resultsSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, astAndSymbolTablePane,
0707 createXPathResultPanel());
0708
0709 JTabbedPane tabbed = new JTabbedPane();
0710 tabbed.addTab("Abstract Syntax Tree / XPath / Symbol Table", resultsSplitPane);
0711 tabbed.addTab("Data Flow Analysis", dfaPanel);
0712 tabbed.setMnemonicAt(0, KeyEvent.VK_A);
0713 tabbed.setMnemonicAt(1, KeyEvent.VK_D);
0714
0715 JSplitPane containerSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, controlSplitPane, tabbed);
0716 containerSplitPane.setContinuousLayout(true);
0717
0718 JMenuBar menuBar = createMenuBar();
0719 frame.setJMenuBar(menuBar);
0720 frame.getContentPane().add(containerSplitPane);
0721 frame.setDefaultCloseOperation(exitOnClose ? JFrame.EXIT_ON_CLOSE : JFrame.DISPOSE_ON_CLOSE);
0722
0723 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
0724 int screenHeight = screenSize.height;
0725 int screenWidth = screenSize.width;
0726
0727 frame.pack();
0728 frame.setSize(screenWidth * 3 / 4, screenHeight * 3 / 4);
0729 frame.setLocation((screenWidth - frame.getWidth()) / 2, (screenHeight - frame.getHeight()) / 2);
0730 frame.setVisible(true);
0731 int horozontalMiddleLocation = controlSplitPane.getMaximumDividerLocation() * 3 / 5;
0732 controlSplitPane.setDividerLocation(horozontalMiddleLocation);
0733 containerSplitPane.setDividerLocation(containerSplitPane.getMaximumDividerLocation() / 2);
0734 astAndSymbolTablePane.setDividerLocation(astAndSymbolTablePane.getMaximumDividerLocation() / 3);
0735 resultsSplitPane.setDividerLocation(horozontalMiddleLocation);
0736
0737 loadSettings();
0738 }
0739
0740 private JMenuBar createMenuBar() {
0741 JMenuBar menuBar = new JMenuBar();
0742 JMenu menu = new JMenu("Language");
0743 ButtonGroup group = new ButtonGroup();
0744
0745 LanguageVersion[] languageVersions = getSupportedLanguageVersions();
0746 for (int i = 0; i < languageVersions.length; i++) {
0747 LanguageVersion languageVersion = languageVersions[i];
0748 JRadioButtonMenuItem button = new JRadioButtonMenuItem(languageVersion.getShortName());
0749 languageVersionMenuItems[i] = button;
0750 group.add(button);
0751 menu.add(button);
0752 }
0753 languageVersionMenuItems[DEFAULT_LANGUAGE_VERSION_SELECTION_INDEX].setSelected(true);
0754 menuBar.add(menu);
0755
0756 JMenu actionsMenu = new JMenu("Actions");
0757 JMenuItem copyXMLItem = new JMenuItem("Copy xml to clipboard");
0758 copyXMLItem.addActionListener(new ActionListener() {
0759 public void actionPerformed(ActionEvent e) {
0760 copyXmlToClipboard();
0761 }
0762 });
0763 actionsMenu.add(copyXMLItem);
0764 JMenuItem createRuleXMLItem = new JMenuItem("Create rule XML");
0765 createRuleXMLItem.addActionListener(new ActionListener() {
0766 public void actionPerformed(ActionEvent e) {
0767 createRuleXML();
0768 }
0769 });
0770 actionsMenu.add(createRuleXMLItem);
0771 menuBar.add(actionsMenu);
0772
0773 return menuBar;
0774 }
0775
0776 private void createRuleXML() {
0777 CreateXMLRulePanel rulePanel = new CreateXMLRulePanel(xpathQueryArea, codeEditorPane);
0778 JFrame xmlframe = new JFrame("Create XML Rule");
0779 xmlframe.setContentPane(rulePanel);
0780 xmlframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
0781 xmlframe.setSize(new Dimension(600, 700));
0782 xmlframe.addComponentListener(new java.awt.event.ComponentAdapter() {
0783 @Override
0784 public void componentResized(ComponentEvent e) {
0785 JFrame tmp = (JFrame) e.getSource();
0786 if (tmp.getWidth() < 600 || tmp.getHeight() < 700) {
0787 tmp.setSize(600, 700);
0788 }
0789 }
0790 });
0791 int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
0792 int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;
0793 xmlframe.pack();
0794 xmlframe.setLocation((screenWidth - xmlframe.getWidth()) / 2, (screenHeight - xmlframe.getHeight()) / 2);
0795 xmlframe.setVisible(true);
0796 }
0797
0798 private JComponent createCodeEditorPanel() {
0799 JPanel p = new JPanel();
0800 p.setLayout(new BorderLayout());
0801 codeEditorPane.setBorder(BorderFactory.createLineBorder(Color.black));
0802 makeTextComponentUndoable(codeEditorPane);
0803
0804 p.add(new JLabel("Source code:"), BorderLayout.NORTH);
0805 p.add(new JScrollPane(codeEditorPane), BorderLayout.CENTER);
0806
0807 return p;
0808 }
0809
0810 private JComponent createASTPanel() {
0811 astTreeWidget.setCellRenderer(createNoImageTreeCellRenderer());
0812 TreeSelectionModel model = astTreeWidget.getSelectionModel();
0813 model.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
0814 model.addTreeSelectionListener(new SymbolTableListener());
0815 model.addTreeSelectionListener(new CodeHighlightListener());
0816 return new JScrollPane(astTreeWidget);
0817 }
0818
0819 private JComponent createXPathResultPanel() {
0820 xpathResults.addElement("No XPath results yet, run an XPath Query first.");
0821 xpathResultList.setBorder(BorderFactory.createLineBorder(Color.black));
0822 xpathResultList.setFixedCellWidth(300);
0823 xpathResultList.setCellRenderer(new ASTListCellRenderer());
0824 xpathResultList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
0825 xpathResultList.getSelectionModel().addListSelectionListener(new ASTSelectionListener());
0826 JScrollPane scrollPane = new JScrollPane();
0827 scrollPane.getViewport().setView(xpathResultList);
0828 return scrollPane;
0829 }
0830
0831 private JPanel createXPathQueryPanel() {
0832 JPanel p = new JPanel();
0833 p.setLayout(new BorderLayout());
0834 xpathQueryArea.setBorder(BorderFactory.createLineBorder(Color.black));
0835 makeTextComponentUndoable(xpathQueryArea);
0836 JScrollPane scrollPane = new JScrollPane(xpathQueryArea);
0837 scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
0838 scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
0839 final JButton b = createGoButton();
0840
0841 JPanel topPanel = new JPanel();
0842 topPanel.setLayout(new BorderLayout());
0843 topPanel.add(new JLabel("XPath Query (if any):"), BorderLayout.WEST);
0844 topPanel.add(createXPathVersionPanel(), BorderLayout.EAST);
0845
0846 p.add(topPanel, BorderLayout.NORTH);
0847 p.add(scrollPane, BorderLayout.CENTER);
0848 p.add(b, BorderLayout.SOUTH);
0849
0850 return p;
0851 }
0852
0853 private JComponent createSymbolTableResultPanel() {
0854 symbolTableTreeWidget.setCellRenderer(createNoImageTreeCellRenderer());
0855 return new JScrollPane(symbolTableTreeWidget);
0856 }
0857
0858 private JPanel createXPathVersionPanel() {
0859 JPanel p = new JPanel();
0860 p.add(new JLabel("XPath Version:"));
0861 for (Object[] values : XPathRule.VERSION_DESCRIPTOR.choices()) {
0862 JRadioButton b = new JRadioButton();
0863 b.setText((String) values[0]);
0864 b.setActionCommand(b.getText());
0865 if (values[0].equals(XPathRule.VERSION_DESCRIPTOR.defaultValue())) {
0866 b.setSelected(true);
0867 }
0868 xpathVersionButtonGroup.add(b);
0869 p.add(b);
0870 }
0871 return p;
0872 }
0873
0874 private JButton createGoButton() {
0875 JButton b = new JButton("Go");
0876 b.setMnemonic('g');
0877 b.addActionListener(new ShowListener());
0878 b.addActionListener(new XPathListener());
0879 b.addActionListener(new DFAListener());
0880 b.addActionListener(new ActionListener() {
0881 public void actionPerformed(ActionEvent e) {
0882 saveSettings();
0883 }
0884 });
0885 return b;
0886 }
0887
0888 private static void makeTextComponentUndoable(JTextComponent textConponent) {
0889 final UndoManager undoManager = new UndoManager();
0890 textConponent.getDocument().addUndoableEditListener(new UndoableEditListener() {
0891 public void undoableEditHappened(UndoableEditEvent evt) {
0892 undoManager.addEdit(evt.getEdit());
0893 }
0894 });
0895 ActionMap actionMap = textConponent.getActionMap();
0896 InputMap inputMap = textConponent.getInputMap();
0897 actionMap.put("Undo", new AbstractAction("Undo") {
0898 public void actionPerformed(ActionEvent evt) {
0899 try {
0900 if (undoManager.canUndo()) {
0901 undoManager.undo();
0902 }
0903 } catch (CannotUndoException e) {
0904 }
0905 }
0906 });
0907 inputMap.put(KeyStroke.getKeyStroke("control Z"), "Undo");
0908
0909 actionMap.put("Redo", new AbstractAction("Redo") {
0910 public void actionPerformed(ActionEvent evt) {
0911 try {
0912 if (undoManager.canRedo()) {
0913 undoManager.redo();
0914 }
0915 } catch (CannotRedoException e) {
0916 }
0917 }
0918 });
0919 inputMap.put(KeyStroke.getKeyStroke("control Y"), "Redo");
0920 }
0921
0922 public static void main(String[] args) {
0923 new Designer(args);
0924 }
0925
0926 private final void copyXmlToClipboard() {
0927 if (codeEditorPane.getText() != null && codeEditorPane.getText().trim().length() > 0) {
0928 String xml = "";
0929 Node cu = getCompilationUnit();
0930 if (cu != null) {
0931 try {
0932 xml = getXmlString(cu);
0933 } catch (TransformerException e) {
0934 e.printStackTrace();
0935 xml = "Error trying to construct XML representation";
0936 }
0937 }
0938 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(xml), this);
0939 }
0940 }
0941
0942 /**
0943 * Returns an unformatted xml string (without the declaration)
0944 *
0945 * @throws TransformerException if the XML cannot be converted to a string
0946 */
0947 private String getXmlString(Node node) throws TransformerException {
0948 StringWriter writer = new StringWriter();
0949
0950 Source source = new DOMSource(node.getAsDocument());
0951 Result result = new StreamResult(writer);
0952 TransformerFactory transformerFactory = TransformerFactory.newInstance();
0953 transformerFactory.setAttribute("indent-number", 3);
0954 Transformer xformer = transformerFactory.newTransformer();
0955 xformer.setOutputProperty(OutputKeys.INDENT, "yes");
0956 xformer.transform(source, result);
0957
0958 return writer.toString();
0959 }
0960
0961 public void lostOwnership(Clipboard clipboard, Transferable contents) {
0962 }
0963
0964 private static final String SETTINGS_FILE_NAME = System.getProperty("user.home")
0965 + System.getProperty("file.separator") + ".pmd_designer.xml";
0966
0967 private void loadSettings() {
0968 try {
0969 File file = new File(SETTINGS_FILE_NAME);
0970 if (file.exists()) {
0971 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
0972 Document document = builder.parse(new FileInputStream(file));
0973 Element settingsElement = document.getDocumentElement();
0974 Element codeElement = (Element) settingsElement.getElementsByTagName("code").item(0);
0975 Element xpathElement = (Element) settingsElement.getElementsByTagName("xpath").item(0);
0976
0977 String code = getTextContext(codeElement);
0978 String languageVersion = codeElement.getAttribute("language-version");
0979 String xpath = getTextContext(xpathElement);
0980 String xpathVersion = xpathElement.getAttribute("version");
0981
0982 codeEditorPane.setText(code);
0983 setLanguageVersion(LanguageVersion.findByTerseName(languageVersion));
0984 xpathQueryArea.setText(xpath);
0985 for (Enumeration<AbstractButton> e = xpathVersionButtonGroup.getElements(); e.hasMoreElements();) {
0986 AbstractButton button = e.nextElement();
0987 if (xpathVersion.equals(button.getActionCommand())) {
0988 button.setSelected(true);
0989 break;
0990 }
0991 }
0992 }
0993 } catch (ParserConfigurationException e) {
0994 e.printStackTrace();
0995 } catch (IOException e) {
0996 e.printStackTrace();
0997 } catch (SAXException e) {
0998 e.printStackTrace();
0999 }
1000 }
1001
1002 private void saveSettings() {
1003 try {
1004 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
1005 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
1006 Document document = documentBuilder.newDocument();
1007
1008 Element settingsElement = document.createElement("settings");
1009 document.appendChild(settingsElement);
1010
1011 Element codeElement = document.createElement("code");
1012 settingsElement.appendChild(codeElement);
1013 codeElement.setAttribute("language-version", getLanguageVersion().getTerseName());
1014 codeElement.appendChild(document.createCDATASection(codeEditorPane.getText()));
1015
1016 Element xpathElement = document.createElement("xpath");
1017 settingsElement.appendChild(xpathElement);
1018 xpathElement.setAttribute("version", xpathVersionButtonGroup.getSelection().getActionCommand());
1019 xpathElement.appendChild(document.createCDATASection(xpathQueryArea.getText()));
1020
1021 TransformerFactory transformerFactory = TransformerFactory.newInstance();
1022 Transformer transformer = transformerFactory.newTransformer();
1023 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
1024 // This is as close to pretty printing as we'll get using standard Java APIs.
1025 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
1026 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3");
1027 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
1028
1029 Source source = new DOMSource(document);
1030 Result result = new StreamResult(new FileWriter(new File(SETTINGS_FILE_NAME)));
1031 transformer.transform(source, result);
1032 } catch (ParserConfigurationException e) {
1033 e.printStackTrace();
1034 } catch (IOException e) {
1035 e.printStackTrace();
1036 } catch (TransformerException e) {
1037 e.printStackTrace();
1038 }
1039 }
1040
1041 private String getTextContext(Element element) {
1042 StringBuilder buf = new StringBuilder();
1043 for (int i = 0; i < element.getChildNodes().getLength(); i++) {
1044 org.w3c.dom.Node child = element.getChildNodes().item(i);
1045 if (child instanceof Text) {
1046 buf.append(((Text)child).getData());
1047 }
1048 }
1049 return buf.toString();
1050 }
1051 }
|