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.StringReader;
    22  import java.io.StringWriter;
    23  import java.lang.reflect.InvocationTargetException;
    24  import java.lang.reflect.Method;
    25  import java.util.ArrayList;
    26  import java.util.Collections;
    27  import java.util.Enumeration;
    28  import java.util.Iterator;
    29  import java.util.List;
    30
    31  import javax.swing.AbstractAction;
    32  import javax.swing.ActionMap;
    33  import javax.swing.BorderFactory;
    34  import javax.swing.ButtonGroup;
    35  import javax.swing.DefaultListModel;
    36  import javax.swing.InputMap;
    37  import javax.swing.JButton;
    38  import javax.swing.JComponent;
    39  import javax.swing.JFrame;
    40  import javax.swing.JLabel;
    41  import javax.swing.JList;
    42  import javax.swing.JMenu;
    43  import javax.swing.JMenuBar;
    44  import javax.swing.JMenuItem;
    45  import javax.swing.JPanel;
    46  import javax.swing.JRadioButtonMenuItem;
    47  import javax.swing.JScrollPane;
    48  import javax.swing.JSplitPane;
    49  import javax.swing.JTabbedPane;
    50  import javax.swing.JTextArea;
    51  import javax.swing.JTree;
    52  import javax.swing.KeyStroke;
    53  import javax.swing.ListCellRenderer;
    54  import javax.swing.ListSelectionModel;
    55  import javax.swing.ScrollPaneConstants;
    56  import javax.swing.WindowConstants;
    57  import javax.swing.event.ListSelectionEvent;
    58  import javax.swing.event.ListSelectionListener;
    59  import javax.swing.event.TreeSelectionEvent;
    60  import javax.swing.event.TreeSelectionListener;
    61  import javax.swing.event.UndoableEditEvent;
    62  import javax.swing.event.UndoableEditListener;
    63  import javax.swing.text.JTextComponent;
    64  import javax.swing.tree.DefaultMutableTreeNode;
    65  import javax.swing.tree.DefaultTreeCellRenderer;
    66  import javax.swing.tree.DefaultTreeModel;
    67  import javax.swing.tree.TreeCellRenderer;
    68  import javax.swing.tree.TreeNode;
    69  import javax.swing.tree.TreePath;
    70  import javax.swing.tree.TreeSelectionModel;
    71  import javax.swing.undo.CannotRedoException;
    72  import javax.swing.undo.CannotUndoException;
    73  import javax.swing.undo.UndoManager;
    74  import javax.xml.transform.OutputKeys;
    75  import javax.xml.transform.Result;
    76  import javax.xml.transform.Source;
    77  import javax.xml.transform.Transformer;
    78  import javax.xml.transform.TransformerException;
    79  import javax.xml.transform.TransformerFactory;
    80  import javax.xml.transform.dom.DOMSource;
    81  import javax.xml.transform.stream.StreamResult;
    82
    83  import net.sourceforge.pmd.PMD;
    84  import net.sourceforge.pmd.RuleContext;
    85  import net.sourceforge.pmd.RuleSet;
    86  import net.sourceforge.pmd.SourceType;
    87  import net.sourceforge.pmd.ast.ASTMethodDeclaration;
    88  import net.sourceforge.pmd.ast.AccessNode;
    89  import net.sourceforge.pmd.ast.Node;
    90  import net.sourceforge.pmd.ast.ParseException;
    91  import net.sourceforge.pmd.ast.SimpleNode;
    92  import net.sourceforge.pmd.jaxen.DocumentNavigator;
    93  import net.sourceforge.pmd.jaxen.MatchesFunction;
    94  import net.sourceforge.pmd.jaxen.TypeOfFunction;
    95  import net.sourceforge.pmd.parsers.Parser;
    96  import net.sourceforge.pmd.sourcetypehandlers.SourceTypeHandler;
    97  import net.sourceforge.pmd.sourcetypehandlers.SourceTypeHandlerBroker;
    98  import net.sourceforge.pmd.symboltable.ClassNameDeclaration;
    99  import net.sourceforge.pmd.symboltable.ClassScope;
    100 import net.sourceforge.pmd.symboltable.LocalScope;
    101 import net.sourceforge.pmd.symboltable.MethodNameDeclaration;
    102 import net.sourceforge.pmd.symboltable.MethodScope;
    103 import net.sourceforge.pmd.symboltable.NameOccurrence;
    104 import net.sourceforge.pmd.symboltable.Scope;
    105 import net.sourceforge.pmd.symboltable.SourceFileScope;
    106 import net.sourceforge.pmd.symboltable.VariableNameDeclaration;
    107 import net.sourceforge.pmd.util.NumericConstants;
    108 import net.sourceforge.pmd.util.StringUtil;
    109
    110 import org.jaxen.BaseXPath;
    111 import org.jaxen.JaxenException;
    112 import org.jaxen.XPath;
    113
    114 public class Designer implements ClipboardOwner {
    115
    116 	private static final char LABEL_IMAGE_SEPARATOR = ':';
    117
    118 	private static final Object[][] sourceTypeSets = new Object[][] {
    119 		{ "JDK 1.3", SourceType.JAVA_13 },
    120 		{ "JDK 1.4", SourceType.JAVA_14 },
    121 		{ "JDK 1.5", SourceType.JAVA_15 },
    122 		{ "JDK 1.6", SourceType.JAVA_16 },
    123 		{ "JDK 1.7", SourceType.JAVA_17 },
    124 		{ "JSP", 	 SourceType.JSP }
    125 		};
    126
    127 	private static final int defaultSourceTypeSelectionIndex = 2; // Java 1.5
    128
    129     private SimpleNode getCompilationUnit() {
    130     	SourceTypeHandler handler = SourceTypeHandlerBroker.getVisitorsFactoryForSourceType(getSourceType());
    131     	Parser parser = handler.getParser();
    132     	parser.setExcludeMarker(PMD.EXCLUDE_MARKER);
    133     	SimpleNode simpleNode = (SimpleNode)parser.parse(new StringReader(codeEditorPane.getText()));
    134     	handler.getSymbolFacade().start(simpleNode);
    135     	handler.getTypeResolutionFacade(null).start(simpleNode);
    136     	return simpleNode;
    137     }
    138
    139     private SourceType getSourceType() {
    140
    141     	return (SourceType)sourceTypeSets[selectedSourceTypeIndex()][1];
    142     }
    143
    144     private int selectedSourceTypeIndex() {
    145     	for (int i=0; i<sourceTypeMenuItems.length; i++) {
    146     		if (sourceTypeMenuItems[i].isSelected()) return i;
    147     	}
    148     	throw new RuntimeException("Initial default source type not specified");
    149     }
    150
    151     private class ExceptionNode implements TreeNode {
    152
    153     	private Object 			item;
    154     	private ExceptionNode[] kids;
    155
    156     	public ExceptionNode(Object theItem) {
    157     		item = theItem;
    158
    159     		if (item instanceof ParseException) createKids();
    160     	}
    161
    162     	// each line in the error message becomes a separate tree node
    163     	private void createKids() {
    164
    165     		String message = ((ParseException)item).getMessage();
    166             String[] lines = StringUtil.substringsOf(message, PMD.EOL);
    167
    168 			kids = new ExceptionNode[lines.length];
    169 			for (int i=0; i<lines.length; i++) {
    170 				kids[i] = new ExceptionNode(lines[i]);
    171 			}
    172     	}
    173
    174 		public int getChildCount() { return kids == null ? 0 : kids.length; }
    175 		public boolean getAllowsChildren() {return false; }
    176 		public boolean isLeaf() { return kids == null; }
    177 		public TreeNode getParent() { return null; }
    178 		public TreeNode getChildAt(int childIndex) { return kids[childIndex]; }
    179 		public String label() {	return item.toString();	}
    180
    181 		public Enumeration children() {
    182 			Enumeration e = new Enumeration() {
    183 				int i = 0;
    184 				public boolean hasMoreElements() {
    185 					return kids != null && i < kids.length;
    186 				}
    187
    188 				public Object nextElement() { return kids[i++]; }
    189 				};
    190 			return e;
    191 		}
    192
    193 		public int getIndex(TreeNode node) {
    194 			for (int i=0; i<kids.length; i++) {
    195 				if (kids[i] == node) return i;
    196 			}
    197 			return -1;
    198 		}
    199     }
    200
    201     // Tree node that wraps the AST node for the tree widget and
    202     // any possible children they may have.
    203     private class ASTTreeNode implements TreeNode {
    204
    205     	private Node 			node;
    206     	private ASTTreeNode 	parent;
    207     	private ASTTreeNode[] 	kids;
    208
    209     	public ASTTreeNode(Node theNode) {
    210     		node = theNode;
    211
    212     		Node prnt = node.jjtGetParent();
    213     		if (prnt != null) parent = new ASTTreeNode(prnt);
    214     	}
    215
    216 		public int getChildCount() { return node.jjtGetNumChildren(); }
    217 		public boolean getAllowsChildren() { return false;	}
    218 		public boolean isLeaf() { return node.jjtGetNumChildren() == 0;	}
    219 		public TreeNode getParent() { return parent; }
    220
    221 		public Scope getScope() {
    222     		if (node instanceof SimpleNode)
    223     			return ((SimpleNode)node).getScope();
    224     		return null;
    225     	}
    226
    227 		public Enumeration children() {
    228
    229 			if (getChildCount() > 0) getChildAt(0);	// force it to build kids
    230
    231 			Enumeration e = new Enumeration() {
    232 				int i = 0;
    233 				public boolean hasMoreElements() {
    234 					return kids != null && i < kids.length;
    235 				}
    236 				public Object nextElement() { return kids[i++]; }
    237 				};
    238 			return e;
    239 		}
    240
    241 		public TreeNode getChildAt(int childIndex) {
    242
    243 			if (kids == null) {
    244 				kids = new ASTTreeNode[node.jjtGetNumChildren()];
    245     			for (int i=0; i<kids.length; i++) {
    246     				kids[i] = new ASTTreeNode(node.jjtGetChild(i));
    247     				}
    248 				}
    249 			return kids[childIndex];
    250 		}
    251
    252 		public int getIndex(TreeNode node) {
    253
    254 			for (int i=0; i<kids.length; i++) {
    255 				if (kids[i] == node) return i;
    256 			}
    257 			return -1;
    258 		}
    259
    260 		public String label() {
    261 			if (node instanceof SimpleNode) {
    262 				SimpleNode sn = (SimpleNode)node;
    263                 if (sn.getLabel() != null) {
    264                     return node.toString() + LABEL_IMAGE_SEPARATOR + sn.getLabel();
    265                 }
    266 				if (sn.getImage() == null) {
    267                     return node.toString();
    268                 }
    269 				return node.toString() + LABEL_IMAGE_SEPARATOR + sn.getImage();
    270 			}
    271 			return node.toString();
    272 		}
    273
    274 		public String getToolTipText() {
    275 			String tooltip = "";
    276 		    if (node instanceof SimpleNode) {
    277 		        SimpleNode sn = (SimpleNode)node;
    278 		        tooltip = "Line: " + sn.getBeginLine() + " Column: " + sn.getBeginColumn();
    279 		    }
    280
    281 		    if (node instanceof AccessNode)
    282 		    {
    283 		    	AccessNode accessNode = (AccessNode)node;
    284 		    	if ( ! "".equals(tooltip))
    285 		    		tooltip += ",";
    286 		    	tooltip += accessNode.isAbstract() ? " Abstract" : "";
    287 		    	tooltip += accessNode.isStatic() ? " Static" : "";
    288 		    	tooltip += accessNode.isFinal() ? " Final" : "";
    289 		    	tooltip += accessNode.isNative() ? " Native" : "";
    290 		    	tooltip += accessNode.isPrivate() ? " Private" : "";
    291 		    	tooltip += accessNode.isSynchronized() ? " Synchronised" : "";
    292 		    	tooltip += accessNode.isTransient() ? " Transient" : "";
    293 		    	tooltip += accessNode.isVolatile() ? " Volatile" : "";
    294 		    	tooltip += accessNode.isStrictfp() ? " Strictfp" : "";
    295 		    }
    296 		    return tooltip;
    297 		}
    298     }
    299
    300     private TreeCellRenderer createNoImageTreeCellRenderer() {
    301     	DefaultTreeCellRenderer treeCellRenderer = new DefaultTreeCellRenderer();
    302     	treeCellRenderer.setLeafIcon(null);
    303     	treeCellRenderer.setOpenIcon(null);
    304     	treeCellRenderer.setClosedIcon(null);
    305     	return treeCellRenderer;
    306     }
    307
    308     // Special tree variant that knows how to retrieve node labels and
    309     // provides the ability to expand all nodes at once.
    310     private class TreeWidget extends JTree {
    311
    312 		private static final long serialVersionUID = 1L;
    313
    314 		public TreeWidget(Object[] items) {
    315     		super(items);
    316             setToolTipText("");
    317     	}
    318
    319         public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
    320         	if (value == null) return "";
    321         	if (value instanceof ASTTreeNode) {
    322         		return ((ASTTreeNode)value).label();
    323         	}
    324         	if (value instanceof ExceptionNode) {
    325         		return ((ExceptionNode)value).label();
    326         	}
    327         	return value.toString();
    328     	}
    329
    330         public String getToolTipText(MouseEvent e) {
    331             if (getRowForLocation(e.getX(), e.getY()) == -1) return null;
    332             TreePath curPath = getPathForLocation(e.getX(), e.getY());
    333             if (curPath.getLastPathComponent() instanceof ASTTreeNode) {
    334             	return ((ASTTreeNode)curPath.getLastPathComponent()).getToolTipText();
    335             } else {
    336             	return super.getToolTipText(e);
    337             }
    338         }
    339
    340         public void expandAll(boolean expand) {
    341             TreeNode root = (TreeNode)getModel().getRoot();
    342             expandAll(new TreePath(root), expand);
    343         }
    344
    345         private void expandAll(TreePath parent, boolean expand) {
    346             // Traverse children
    347             TreeNode node = (TreeNode)parent.getLastPathComponent();
    348             if (node.getChildCount() >= 0) {
    349                 for (Enumeration e=node.children(); e.hasMoreElements(); ) {
    350                     TreeNode n = (TreeNode)e.nextElement();
    351                     TreePath path = parent.pathByAddingChild(n);
    352                     expandAll(path, expand);
    353                 }
    354             }
    355
    356             if (expand) {
    357                 expandPath(parent);
    358             } else {
    359                 collapsePath(parent);
    360             }
    361         }
    362     }
    363
    364     private void loadASTTreeData(TreeNode rootNode) {
    365     	astTreeWidget.setModel(new DefaultTreeModel(rootNode));
    366     	astTreeWidget.expandAll(true);
    367     }
    368
    369     private void loadSymbolTableTreeData(TreeNode rootNode) {
    370     	if (rootNode != null) {
    371 	    	symbolTableTreeWidget.setModel(new DefaultTreeModel(rootNode));
    372 	    	symbolTableTreeWidget.expandAll(true);
    373     	} else {
    374     		symbolTableTreeWidget.setModel(null);
    375     	}
    376     }
    377
    378     private class ShowListener implements ActionListener {
    379         public void actionPerformed(ActionEvent ae) {
    380             MyPrintStream ps = new MyPrintStream();
    381             System.setOut(ps);
    382             TreeNode tn;
    383             try {
    384                 SimpleNode lastCompilationUnit = getCompilationUnit();
    385                 tn = new ASTTreeNode(lastCompilationUnit);
    386             } catch (ParseException pe) {
    387             	tn = new ExceptionNode(pe);
    388             	}
    389
    390             loadASTTreeData(tn);
    391             loadSymbolTableTreeData(null);
    392         }
    393     }
    394
    395     private class DFAListener implements ActionListener {
    396         public void actionPerformed(ActionEvent ae) {
    397
    398            DFAGraphRule dfaGraphRule = new DFAGraphRule();
    399            RuleSet rs = new RuleSet();
    400            SourceType sourceType = getSourceType();
    401            if (!sourceType.equals(SourceType.JSP)){
    402                rs.addRule(dfaGraphRule);
    403            }
    404            RuleContext ctx = new RuleContext();
    405            ctx.setSourceCodeFilename("[no filename]");
    406            StringReader reader = new StringReader(codeEditorPane.getText());
    407            PMD pmd = new PMD();
    408            pmd.setJavaVersion(sourceType);
    409
    410            try {
    411                 pmd.processFile(reader, rs, ctx);
    412 //           } catch (PMDException pmde) {
    413 //               loadTreeData(new ExceptionNode(pmde));
    414            } catch (Exception e) {
    415                e.printStackTrace();
    416            		}
    417
    418            List<ASTMethodDeclaration> methods = dfaGraphRule.getMethods();
    419            if (methods != null && !methods.isEmpty()) {
    420                dfaPanel.resetTo(methods, codeEditorPane);
    421                dfaPanel.repaint();
    422            }
    423         }
    424     }
    425
    426     private class XPathListener implements ActionListener {
    427         public void actionPerformed(ActionEvent ae) {
    428             xpathResults.clear();
    429             if (xpathQueryArea.getText().length() == 0) {
    430                 xpathResults.addElement("XPath query field is empty.");
    431                 xpathResultList.repaint();
    432                 codeEditorPane.requestFocus();
    433                 return;
    434             }
    435             SimpleNode c = getCompilationUnit();
    436             try {
    437                 XPath xpath = new BaseXPath(xpathQueryArea.getText(), new DocumentNavigator());
    438                 for (Iterator iter = xpath.selectNodes(c).iterator(); iter.hasNext();) {
    439                     Object obj = iter.next();
    440                     if (obj instanceof String) {
    441                         System.out.println("Result was a string: " + ((String) obj));
    442                     } else if (!(obj instanceof Boolean)) {
    443                         // if it's a Boolean and it's 'false', what does that mean?
    444                         xpathResults.addElement(obj);
    445                     }
    446                 }
    447                 if (xpathResults.isEmpty()) {
    448                     xpathResults.addElement("No matching nodes " + System.currentTimeMillis());
    449                 }
    450             } catch (ParseException pe) {
    451                 xpathResults.addElement(pe.fillInStackTrace().getMessage());
    452             } catch (JaxenException je) {
    453                 xpathResults.addElement(je.fillInStackTrace().getMessage());
    454             }
    455             xpathResultList.repaint();
    456             xpathQueryArea.requestFocus();
    457         }
    458     }
    459
    460     private class SymbolTableListener implements TreeSelectionListener {
    461     	public void valueChanged(TreeSelectionEvent e) {
    462     		if (e.getNewLeadSelectionPath() != null) {
    463     			ASTTreeNode astTreeNode = (ASTTreeNode)e.getNewLeadSelectionPath().getLastPathComponent();
    464
    465     			DefaultMutableTreeNode symbolTableTreeNode = new DefaultMutableTreeNode();
    466     			DefaultMutableTreeNode selectedAstTreeNode = new DefaultMutableTreeNode("AST Node: " + astTreeNode.label());
    467     			symbolTableTreeNode.add(selectedAstTreeNode);
    468
    469 	    		List<Scope> scopes = new ArrayList<Scope>();
    470 	    		Scope scope = astTreeNode.getScope();
    471 	    		while (scope != null)
    472 	    		{
    473 	    			scopes.add(scope);
    474 	    			scope = scope.getParent();
    475 	    		}
    476 	    		Collections.reverse(scopes);
    477 	    		for (int i = 0; i < scopes.size(); i++) {
    478 	    			scope = scopes.get(i);
    479 	    			DefaultMutableTreeNode scopeTreeNode =  new DefaultMutableTreeNode("Scope: " + scope.getClass().getSimpleName());
    480 	    			selectedAstTreeNode.add(scopeTreeNode);
    481 	    			if (!(scope instanceof MethodScope || scope instanceof LocalScope)) {
    482 	    				if (!scope.getClassDeclarations().isEmpty()) {
    483 				    		for (ClassNameDeclaration classNameDeclaration: scope.getClassDeclarations().keySet()) {
    484 				    			DefaultMutableTreeNode classNameDeclarationTreeNode = new DefaultMutableTreeNode("Class name declaration: " + classNameDeclaration);
    485 				    			scopeTreeNode.add(classNameDeclarationTreeNode);
    486 				    			for (NameOccurrence nameOccurrence: scope.getClassDeclarations().get(classNameDeclaration)) {
    487 					    			DefaultMutableTreeNode nameOccurenceTreeNode = new DefaultMutableTreeNode("Name occurrence: " + nameOccurrence);
    488 					    			classNameDeclarationTreeNode.add(nameOccurenceTreeNode);
    489 				    			}
    490 				    		}
    491 	    				}
    492 	    			}
    493 	    			if (scope instanceof ClassScope) {
    494 	    				ClassScope classScope = (ClassScope)scope;
    495 	    				if (!classScope.getMethodDeclarations().isEmpty()) {
    496 				    		for (MethodNameDeclaration methodNameDeclaration: classScope.getMethodDeclarations().keySet()) {
    497 				    			DefaultMutableTreeNode methodNameDeclarationTreeNode = new DefaultMutableTreeNode("Method name declaration: " + methodNameDeclaration);
    498 				    			scopeTreeNode.add(methodNameDeclarationTreeNode);
    499 				    			for (NameOccurrence nameOccurrence: classScope.getMethodDeclarations().get(methodNameDeclaration)) {
    500 					    			DefaultMutableTreeNode nameOccurenceTreeNode = new DefaultMutableTreeNode("Name occurrence: " + nameOccurrence);
    501 					    			methodNameDeclarationTreeNode.add(nameOccurenceTreeNode);
    502 				    			}
    503 				    		}
    504 	    				}
    505 	    			}
    506 	    			if (!(scope instanceof SourceFileScope)) {
    507 	    				if (!scope.getVariableDeclarations().isEmpty()) {
    508 				    		for (VariableNameDeclaration variableNameDeclaration: scope.getVariableDeclarations().keySet()) {
    509 				    			DefaultMutableTreeNode variableNameDeclarationTreeNode = new DefaultMutableTreeNode("Variable name declaration: " + variableNameDeclaration);
    510 				    			scopeTreeNode.add(variableNameDeclarationTreeNode);
    511 				    			for (NameOccurrence nameOccurrence: scope.getVariableDeclarations().get(variableNameDeclaration)) {
    512 					    			DefaultMutableTreeNode nameOccurenceTreeNode = new DefaultMutableTreeNode("Name occurrence: " + nameOccurrence);
    513 					    			variableNameDeclarationTreeNode.add(nameOccurenceTreeNode);
    514 				    			}
    515 				    		}
    516 	    				}
    517 	    			}
    518 	    		}
    519 	    		loadSymbolTableTreeData(symbolTableTreeNode);
    520     		}
    521         }
    522     }
    523
    524     private class CodeHighlightListener implements TreeSelectionListener {
    525         public void valueChanged(TreeSelectionEvent e) {
    526             if (e.getNewLeadSelectionPath() != null) {
    527                 ASTTreeNode selected = (ASTTreeNode)e.getNewLeadSelectionPath().getLastPathComponent();
    528                 if (selected != null && selected.node instanceof SimpleNode) {
    529                     SimpleNode node = (SimpleNode) selected.node;
    530
    531                     codeEditorPane.select(node);
    532                 }
    533             }
    534         }
    535     }
    536
    537     private class ASTListCellRenderer extends JLabel implements ListCellRenderer {
    538         private static final long serialVersionUID = 1L;
    539
    540         public Component getListCellRendererComponent(
    541                 JList list,
    542                 Object value,
    543                 int index,
    544                 boolean isSelected,
    545                 boolean cellHasFocus) {
    546
    547             if (isSelected) {
    548                 setBackground(list.getSelectionBackground());
    549                 setForeground(list.getSelectionForeground());
    550             } else {
    551                 setBackground(list.getBackground());
    552                 setForeground(list.getForeground());
    553             }
    554
    555             String text;
    556             if (value instanceof SimpleNode) {
    557                 SimpleNode node = (SimpleNode) value;
    558                 StringBuffer sb = new StringBuffer();
    559                 String name = node.getClass().getName().substring(node.getClass().getName().lastIndexOf('.') + 1);
    560                 sb.append(name)
    561                     .append(" at line ").append(node.getBeginLine())
    562                     .append(" column ").append(node.getBeginColumn())
    563                     .append(PMD.EOL);
    564                 text = sb.toString();
    565             } else {
    566                 text = value.toString();
    567             }
    568             setText(text);
    569             return this;
    570         }
    571     }
    572
    573     private class ASTSelectionListener implements ListSelectionListener {
    574         public void valueChanged(ListSelectionEvent e) {
    575             ListSelectionModel lsm = (ListSelectionModel)e.getSource();
    576             if (!lsm.isSelectionEmpty()) {
    577                 Object o = xpathResults.get(lsm.getMinSelectionIndex());
    578                 if (o instanceof SimpleNode) {
    579                     codeEditorPane.select((SimpleNode) o);
    580                 }
    581             }
    582         }
    583     }
    584
    585 	private boolean exitOnClose = true;
    586     private final CodeEditorTextPane codeEditorPane = new CodeEditorTextPane();
    587     private final TreeWidget astTreeWidget			= new TreeWidget(new Object[0]);
    588     private DefaultListModel xpathResults			= new DefaultListModel();
    589     private final JList xpathResultList				= new JList(xpathResults);
    590     private final JTextArea xpathQueryArea			= new JTextArea(15, 30);
    591     private final TreeWidget symbolTableTreeWidget	= new TreeWidget(new Object[0]);
    592     private final JFrame frame 						= new JFrame("PMD Rule Designer (v " + PMD.VERSION + ')');
    593     private final DFAPanel dfaPanel					= new DFAPanel();
    594     private final JRadioButtonMenuItem[] sourceTypeMenuItems = new JRadioButtonMenuItem[sourceTypeSets.length];
    595
    596 	public Designer(String[] args) {
    597 		if (args.length > 0) {
    598 			exitOnClose = !args[0].equals("-noexitonclose");
    599 		}
    600
    601 		MatchesFunction.registerSelfInSimpleContext();
    602         TypeOfFunction.registerSelfInSimpleContext();
    603
    604         xpathQueryArea.setFont(new Font("Verdana", Font.PLAIN, 16));
    605         JSplitPane controlSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, createCodeEditorPanel(), createXPathQueryPanel());
    606
    607         JSplitPane astAndSymbolTablePane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, createASTPanel(), createSymbolTableResultPanel());
    608
    609         JSplitPane resultsSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, astAndSymbolTablePane, createXPathResultPanel());
    610
    611         JTabbedPane tabbed = new JTabbedPane();
    612         tabbed.addTab("Abstract Syntax Tree / XPath / Symbol Table", resultsSplitPane);
    613         tabbed.addTab("Data Flow Analysis", dfaPanel);
    614         try {
    615             // Remove when minimal runtime support is >= JDK 1.4
    616             Method setMnemonicAt = JTabbedPane.class.getMethod("setMnemonicAt", new Class[]{Integer.TYPE, Integer.TYPE});
    617             if (setMnemonicAt != null) {
    618                 //        // Compatible with >= JDK 1.4
    619                 //        tabbed.setMnemonicAt(0, KeyEvent.VK_A);
    620                 //        tabbed.setMnemonicAt(1, KeyEvent.VK_D);
    621                 setMnemonicAt.invoke(tabbed, new Object[]{NumericConstants.ZERO, KeyEvent.VK_A});
    622                 setMnemonicAt.invoke(tabbed, new Object[]{NumericConstants.ONE, KeyEvent.VK_D});
    623             }
    624         } catch (NoSuchMethodException nsme) { // Runtime is < JDK 1.4
    625         } catch (IllegalAccessException e) { // Runtime is >= JDK 1.4 but there was an error accessing the function
    626             e.printStackTrace();
    627             throw new InternalError("Runtime reports to be >= JDK 1.4 yet String.split(java.lang.String) is broken.");
    628         } catch (IllegalArgumentException e) {
    629             e.printStackTrace();
    630             throw new InternalError("Runtime reports to be >= JDK 1.4 yet String.split(java.lang.String) is broken.");
    631         } catch (InvocationTargetException e) { // Runtime is >= JDK 1.4 but there was an error accessing the function
    632             e.printStackTrace();
    633             throw new InternalError("Runtime reports to be >= JDK 1.4 yet String.split(java.lang.String) is broken.");
    634         }
    635
    636         JSplitPane containerSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, controlSplitPane, tabbed);
    637         containerSplitPane.setContinuousLayout(true);
    638
    639         JMenuBar menuBar = createMenuBar();
    640         frame.setJMenuBar(menuBar);
    641         frame.getContentPane().add(containerSplitPane);
    642         frame.setDefaultCloseOperation(exitOnClose ? JFrame.EXIT_ON_CLOSE : JFrame.DISPOSE_ON_CLOSE);
    643
    644         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    645         int screenHeight = screenSize.height;
    646         int screenWidth = screenSize.width;
    647
    648         frame.pack();
    649         frame.setSize((screenWidth*3/4),(screenHeight*3/4));
    650         frame.setLocation((screenWidth -frame.getWidth()) / 2, (screenHeight  - frame.getHeight()) / 2);
    651         frame.setVisible(true);
    652         int horozontalMiddleLocation = controlSplitPane.getMaximumDividerLocation() * 3 / 5;
    653         controlSplitPane.setDividerLocation(horozontalMiddleLocation);
    654         containerSplitPane.setDividerLocation(containerSplitPane.getMaximumDividerLocation() / 2);
    655         astAndSymbolTablePane.setDividerLocation(astAndSymbolTablePane.getMaximumDividerLocation()/3);
    656         resultsSplitPane.setDividerLocation(horozontalMiddleLocation);
    657     }
    658
    659     private JMenuBar createMenuBar() {
    660         JMenuBar menuBar = new JMenuBar();
    661         JMenu menu = new JMenu("JDK");
    662         ButtonGroup group = new ButtonGroup();
    663
    664         for (int i=0; i<sourceTypeSets.length; i++) {
    665         	JRadioButtonMenuItem button = new JRadioButtonMenuItem(sourceTypeSets[i][0].toString());
    666         	sourceTypeMenuItems[i] = button;
    667         	group.add(button);
    668         	menu.add(button);
    669         }
    670         sourceTypeMenuItems[defaultSourceTypeSelectionIndex].setSelected(true);
    671         menuBar.add(menu);
    672
    673         JMenu actionsMenu = new JMenu("Actions");
    674         JMenuItem copyXMLItem = new JMenuItem("Copy xml to clipboard");
    675         copyXMLItem.addActionListener(new ActionListener() {
    676             public void actionPerformed(ActionEvent e) {
    677                 copyXmlToClipboard();
    678             }
    679         });
    680         actionsMenu.add(copyXMLItem);
    681         JMenuItem createRuleXMLItem = new JMenuItem("Create rule XML");
    682         createRuleXMLItem.addActionListener(new ActionListener() {
    683             public void actionPerformed(ActionEvent e) {
    684                 createRuleXML();
    685             }
    686         });
    687         actionsMenu.add(createRuleXMLItem);
    688         menuBar.add(actionsMenu);
    689
    690         return menuBar;
    691     }
    692
    693     private void createRuleXML() {
    694     	CreateXMLRulePanel rulePanel = new CreateXMLRulePanel(xpathQueryArea, codeEditorPane);
    695     	JFrame xmlframe = new JFrame("Create XML Rule");
    696     	xmlframe.setContentPane(rulePanel);
    697     	xmlframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    698         xmlframe.setSize(new Dimension(600, 700));
    699         xmlframe.addComponentListener(new java.awt.event.ComponentAdapter() {
    700         	  public void componentResized(ComponentEvent e) {
    701         	    JFrame tmp = (JFrame)e.getSource();
    702         	    if (tmp.getWidth()<600 || tmp.getHeight()<700) {
    703         	      tmp.setSize(600, 700);
    704         	    }
    705         	  }
    706         	});
    707         int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
    708         int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;
    709         xmlframe.pack();
    710         xmlframe.setLocation((screenWidth - xmlframe.getWidth()) / 2, (screenHeight - xmlframe.getHeight()) / 2);
    711         xmlframe.setVisible(true);
    712     }
    713
    714     private JComponent createCodeEditorPanel()
    715     {
    716         JPanel p = new JPanel();
    717         p.setLayout(new BorderLayout());
    718         codeEditorPane.setBorder(BorderFactory.createLineBorder(Color.black));
    719         makeTextComponentUndoable(codeEditorPane);
    720
    721         p.add(new JLabel("Source code:"), BorderLayout.NORTH);
    722         p.add(new JScrollPane(codeEditorPane), BorderLayout.CENTER);
    723
    724         return p;
    725     }
    726
    727     private JComponent createASTPanel() {
    728     	astTreeWidget.setCellRenderer(createNoImageTreeCellRenderer());
    729     	TreeSelectionModel model = astTreeWidget.getSelectionModel();
    730     	model.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
    731     	model.addTreeSelectionListener(new SymbolTableListener());
    732     	model.addTreeSelectionListener(new CodeHighlightListener());
    733         return new JScrollPane(astTreeWidget);
    734     }
    735
    736     private JComponent createXPathResultPanel() {
    737         xpathResults.addElement("No XPath results yet, run an XPath Query first.");
    738         xpathResultList.setBorder(BorderFactory.createLineBorder(Color.black));
    739         xpathResultList.setFixedCellWidth(300);
    740         xpathResultList.setCellRenderer(new ASTListCellRenderer());
    741         xpathResultList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    742         xpathResultList.getSelectionModel().addListSelectionListener(new ASTSelectionListener());
    743         JScrollPane scrollPane = new JScrollPane();
    744         scrollPane.getViewport().setView(xpathResultList);
    745         return scrollPane;
    746     }
    747
    748     private JPanel createXPathQueryPanel() {
    749         JPanel p = new JPanel();
    750         p.setLayout(new BorderLayout());
    751         xpathQueryArea.setBorder(BorderFactory.createLineBorder(Color.black));
    752         makeTextComponentUndoable(xpathQueryArea);
    753         JScrollPane scrollPane = new JScrollPane(xpathQueryArea);
    754         scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    755         scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
    756         final JButton b = createGoButton();
    757
    758         p.add(new JLabel("XPath Query (if any):"), BorderLayout.NORTH);
    759         p.add(scrollPane, BorderLayout.CENTER);
    760         p.add(b, BorderLayout.SOUTH);
    761
    762         return p;
    763     }
    764
    765     private JComponent createSymbolTableResultPanel() {
    766     	symbolTableTreeWidget.setCellRenderer(createNoImageTreeCellRenderer());
    767         return new JScrollPane(symbolTableTreeWidget);
    768     }
    769
    770     private JButton createGoButton() {
    771         JButton b = new JButton("Go");
    772         b.setMnemonic('g');
    773         b.addActionListener(new ShowListener());
    774         b.addActionListener(codeEditorPane);
    775         b.addActionListener(new XPathListener());
    776         b.addActionListener(new DFAListener());
    777         return b;
    778     }
    779
    780     private static void makeTextComponentUndoable(JTextComponent textConponent) {
    781         final UndoManager undoManager = new UndoManager();
    782         textConponent.getDocument().addUndoableEditListener(new UndoableEditListener() {
    783 			     public void undoableEditHappened(
    784 			       UndoableEditEvent evt) {
    785 			         undoManager.addEdit(evt.getEdit());
    786 			     }
    787   			 });
    788         ActionMap actionMap = textConponent.getActionMap();
    789         InputMap inputMap = textConponent.getInputMap();
    790         actionMap.put("Undo", new AbstractAction("Undo") {
    791 		         public void actionPerformed(ActionEvent evt) {
    792 		             try {
    793 		                 if (undoManager.canUndo()) {
    794 		                     undoManager.undo();
    795 		                 }
    796 		             } catch (CannotUndoException e) {
    797 		             }
    798 		         }
    799         	 });
    800         inputMap.put(KeyStroke.getKeyStroke("control Z"), "Undo");
    801
    802         actionMap.put("Redo", new AbstractAction("Redo") {
    803 			    public void actionPerformed(ActionEvent evt) {
    804 			        try {
    805 			            if (undoManager.canRedo()) {
    806 			                undoManager.redo();
    807 			            }
    808 			        } catch (CannotRedoException e) {
    809 			        }
    810 			    }
    811             });
    812         inputMap.put(KeyStroke.getKeyStroke("control Y"), "Redo");
    813     }
    814
    815     public static void main(String[] args) {
    816         new Designer(args);
    817     }
    818
    819     private final void copyXmlToClipboard() {
    820         if (codeEditorPane.getText() != null && codeEditorPane.getText().trim().length() > 0) {
    821             String xml = "";
    822             SimpleNode cu = getCompilationUnit();
    823             if (cu != null) {
    824                 try {
    825                     xml = getXmlString(cu);
    826                 } catch (TransformerException e) {
    827                     e.printStackTrace();
    828                     xml = "Error trying to construct XML representation";
    829                 }
    830             }
    831             Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(xml), this);
    832         }
    833     }
    834
    835     /**
    836      * Returns an unformatted xml string (without the declaration)
    837      *
    838      * @throws TransformerException if the XML cannot be converted to a string
    839      */
    840     private String getXmlString(SimpleNode node) throws TransformerException {
    841         StringWriter writer = new StringWriter();
    842
    843         Source source = new DOMSource(node.asXml());
    844         Result result = new StreamResult(writer);
    845         TransformerFactory transformerFactory = TransformerFactory.newInstance();
    846         try {
    847             transformerFactory.setAttribute("indent-number", 4);   //For java 5
    848         } catch (IllegalArgumentException e) {
    849             //Running on Java 1.4 which does not support this attribute
    850         }
    851         Transformer xformer = transformerFactory.newTransformer();
    852         xformer.setOutputProperty(OutputKeys.INDENT, "yes");
    853         xformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "4");   //For java 1.4
    854         xformer.transform(source, result);
    855
    856         return writer.toString();
    857     }
    858
    859     public void lostOwnership(Clipboard clipboard, Transferable contents) {
    860     }
    861 }
    862