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 net.sourceforge.pmd.PMD;
7   import net.sourceforge.pmd.RuleContext;
8   import net.sourceforge.pmd.RuleSet;
9   import net.sourceforge.pmd.SourceType;
10  import net.sourceforge.pmd.TargetJDK1_3;
11  import net.sourceforge.pmd.TargetJDK1_4;
12  import net.sourceforge.pmd.TargetJDK1_5;
13  import net.sourceforge.pmd.TargetJDK1_6;
14  import net.sourceforge.pmd.ast.Node;
15  import net.sourceforge.pmd.ast.ParseException;
16  import net.sourceforge.pmd.ast.SimpleNode;
17  import net.sourceforge.pmd.jaxen.DocumentNavigator;
18  import net.sourceforge.pmd.jaxen.MatchesFunction;
19  import net.sourceforge.pmd.jsp.ast.JspCharStream;
20  import net.sourceforge.pmd.jsp.ast.JspParser;
21  import net.sourceforge.pmd.util.NumericConstants;
22  import net.sourceforge.pmd.util.StringUtil;
23  import org.jaxen.BaseXPath;
24  import org.jaxen.JaxenException;
25  import org.jaxen.XPath;
26  
27  import javax.swing.*;
28  import javax.swing.event.*;
29  import javax.swing.text.JTextComponent;
30  import javax.swing.tree.DefaultTreeCellRenderer;
31  import javax.swing.tree.DefaultTreeModel;
32  import javax.swing.tree.TreeNode;
33  import javax.swing.tree.TreePath;
34  import javax.swing.undo.*;
35  import java.awt.BorderLayout;
36  import java.awt.Color;
37  import java.awt.Component;
38  import java.awt.Dimension;
39  import java.awt.Font;
40  import java.awt.FontMetrics;
41  import java.awt.Graphics;
42  import java.awt.Toolkit;
43  import java.awt.datatransfer.Clipboard;
44  import java.awt.datatransfer.ClipboardOwner;
45  import java.awt.datatransfer.StringSelection;
46  import java.awt.datatransfer.Transferable;
47  import java.awt.event.ActionEvent;
48  import java.awt.event.ActionListener;
49  import java.awt.event.ComponentEvent;
50  import java.awt.event.KeyEvent;
51  import java.io.IOException;
52  import java.io.StringReader;
53  import java.io.StringWriter;
54  import java.lang.reflect.InvocationTargetException;
55  import java.lang.reflect.Method;
56  import java.util.Enumeration;
57  import java.util.Iterator;
58  import java.util.List;
59  import java.util.Vector;
60  
61  public class Designer implements ClipboardOwner {
62  
63  	private static final char LABEL_IMAGE_SEPARATOR = ':';
64  	private static final Color IMAGE_TEXT_COLOR = Color.BLUE;
65  
66  	private interface Parser { public SimpleNode parse(StringReader sr); };
67  
68  	private static final Parser jdkParser1_3 = new Parser() {
69  		public SimpleNode parse(StringReader sr) { return new TargetJDK1_3().createParser(sr).CompilationUnit(); };
70  	};
71  	
72  	private static final Parser jdkParser1_4 = new Parser() {
73  		public SimpleNode parse(StringReader sr) { return new TargetJDK1_4().createParser(sr).CompilationUnit(); };
74  	};
75  	
76  	private static final Parser jdkParser1_5 = new Parser() {
77  		public SimpleNode parse(StringReader sr) { return new TargetJDK1_5().createParser(sr).CompilationUnit(); };
78  	};
79  	
80  	private static final Parser jdkParser1_6 = new Parser() {
81  		public SimpleNode parse(StringReader sr) { return new TargetJDK1_6().createParser(sr).CompilationUnit(); };
82  	};
83  	
84  	private static final Parser jspParser = new Parser() {
85  		public SimpleNode parse(StringReader sr) { return new JspParser(new JspCharStream(sr)).CompilationUnit(); };
86  	};
87  	
88  	private static final Object[][] sourceTypeSets = new Object[][] {
89  		{ "JDK 1.3", SourceType.JAVA_13, jdkParser1_3 },
90  		{ "JDK 1.4", SourceType.JAVA_14, jdkParser1_4 },
91  		{ "JDK 1.5", SourceType.JAVA_15, jdkParser1_5 },
92  		{ "JDK 1.6", SourceType.JAVA_16, jdkParser1_6 },
93  		{ "JSP", 	 SourceType.JSP, 	 jspParser }
94  		};
95  	
96  	private static final int defaultSourceTypeSelectionIndex = 1; // JDK 1.4
97  	
98  
99      private SimpleNode getCompilationUnit() {
100     	    	
101     	Parser parser = (Parser)sourceTypeSets[selectedSourceTypeIndex()][2];
102     	return parser.parse(new StringReader(codeEditorPane.getText()));
103     }
104     
105     private SourceType getSourceType() {
106     	
107     	return (SourceType)sourceTypeSets[selectedSourceTypeIndex()][1];
108     }
109     
110     private int selectedSourceTypeIndex() {
111     	for (int i=0; i<sourceTypeMenuItems.length; i++) {
112     		if (sourceTypeMenuItems[i].isSelected()) return i;
113     	}
114     	throw new RuntimeException("Initial default source type not specified");
115     }
116     
117     private class ExceptionNode implements TreeNode {
118 
119     	private Object 			item;    	
120     	private ExceptionNode[] kids;
121     	
122     	public ExceptionNode(Object theItem) {
123     		item = theItem;
124     		
125     		if (item instanceof ParseException) createKids();
126     	}
127     	
128     	// each line in the error message becomes a separate tree node
129     	private void createKids() {
130     		    		
131     		String message = ((ParseException)item).getMessage();    		
132             String[] lines = StringUtil.substringsOf(message, PMD.EOL);
133 
134 			kids = new ExceptionNode[lines.length];
135 			for (int i=0; i<lines.length; i++) {
136 				kids[i] = new ExceptionNode(lines[i]);
137 			}
138     	}
139     	
140 		public int getChildCount() { return kids == null ? 0 : kids.length; }
141 		public boolean getAllowsChildren() {return false; }
142 		public boolean isLeaf() { return kids == null; }
143 		public TreeNode getParent() { return null; }
144 		public TreeNode getChildAt(int childIndex) { return kids[childIndex]; }
145 		public String label() {	return item.toString();	}
146 		
147 		public Enumeration children() {
148 			Enumeration e = new Enumeration() {
149 				int i = 0;
150 				public boolean hasMoreElements() { 
151 					return kids != null && i < kids.length;
152 				}
153 
154 				public Object nextElement() { return kids[i++]; }
155 				};
156 			return e;
157 		}
158 		
159 		public int getIndex(TreeNode node) {
160 			for (int i=0; i<kids.length; i++) {
161 				if (kids[i] == node) return i;
162 			}
163 			return -1;
164 		}
165     }
166     
167     // Tree node that wraps the AST node for the tree widget and
168     // any possible children they may have.
169     private class ASTTreeNode implements TreeNode {
170 
171     	private Node 			node;
172     	private ASTTreeNode 	parent;
173     	private ASTTreeNode[] 	kids;
174     	
175     	public ASTTreeNode(Node theNode) {
176     		node = theNode;
177     		
178     		Node prnt = node.jjtGetParent();
179     		if (prnt != null) parent = new ASTTreeNode(prnt);    		
180     	}
181     	
182 		public int getChildCount() { return node.jjtGetNumChildren(); }
183 		public boolean getAllowsChildren() { return false;	}
184 		public boolean isLeaf() { return node.jjtGetNumChildren() == 0;	}
185 		public TreeNode getParent() { return parent; }
186 		
187 		public Enumeration children() {
188 			
189 			if (getChildCount() > 0) getChildAt(0);	// force it to build kids
190 			
191 			Enumeration e = new Enumeration() {
192 				int i = 0;
193 				public boolean hasMoreElements() { 
194 					return kids != null && i < kids.length;
195 				}
196 				public Object nextElement() { return kids[i++]; }
197 				};
198 			return e;
199 		}
200 
201 		public TreeNode getChildAt(int childIndex) {
202 			
203 			if (kids == null) {
204 				kids = new ASTTreeNode[node.jjtGetNumChildren()];
205     			for (int i=0; i<kids.length; i++) {
206     				kids[i] = new ASTTreeNode(node.jjtGetChild(i));
207     				}
208 				}			
209 			return kids[childIndex];
210 		}
211 
212 		public int getIndex(TreeNode node) {
213 
214 			for (int i=0; i<kids.length; i++) {
215 				if (kids[i] == node) return i;
216 			}
217 			return -1;
218 		}
219     	
220 		public String label() {
221 			if (node instanceof SimpleNode) {
222 				SimpleNode sn = (SimpleNode)node;
223                 if (sn.getLabel() != null) {
224                     return node.toString() + LABEL_IMAGE_SEPARATOR + sn.getLabel();
225                 }
226 				if (sn.getImage() == null) {
227                     return node.toString();
228                 }
229 				return node.toString() + LABEL_IMAGE_SEPARATOR + sn.getImage();
230 			}
231 			return node.toString();
232 		}
233     }
234     
235     // Create our own renderer to ensure we don't show the default icon
236     // and render any node image data in a different colour to hightlight 
237     // it.
238     
239     private class ASTCellRenderer extends DefaultTreeCellRenderer {
240     	
241     	private ASTTreeNode node;
242 
243     	public Icon getIcon() { return null; };
244     	
245     	public Component getTreeCellRendererComponent(JTree tree, Object value,	boolean sel,boolean expanded,boolean leaf, int row,  boolean hasFocus) {
246 
247     		if (value instanceof ASTTreeNode) {
248     			node = (ASTTreeNode)value;
249     		}
250     		return super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
251     	}
252     	
253     	// overwrite the image data (if present) in a different colour
254     	public void paint(Graphics g) {
255     		
256     		super.paint(g);
257     		
258     		if (node == null) return;
259     		
260     		String text = node.label();
261     		int separatorPos = text.indexOf(LABEL_IMAGE_SEPARATOR);
262     		if (separatorPos < 0) return;
263     		    		
264     		String label = text.substring(0, separatorPos+1);
265     		String image = text.substring(separatorPos+1);
266 
267     		FontMetrics fm = g.getFontMetrics();
268     		int width = SwingUtilities.computeStringWidth(fm, label);
269     		
270     		g.setColor(IMAGE_TEXT_COLOR);
271     		g.drawString(image, width, fm.getMaxAscent());
272     	}
273     }
274     
275     // Special tree variant that knows how to retrieve node labels and
276     // provides the ability to expand all nodes at once.
277     
278     private class ASTTreeWidget extends JTree {
279     	
280     	public ASTTreeWidget(Vector items) {
281     		super(items);
282     	}
283     	
284         public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
285         	if (value == null) return "";
286         	if (value instanceof ASTTreeNode) {
287         		return ((ASTTreeNode)value).label();
288         	}        	
289         	if (value instanceof ExceptionNode) {
290         		return ((ExceptionNode)value).label();
291         	}        	
292         	return value.toString();
293     	}
294         
295         public void expandAll(boolean expand) {
296             TreeNode root = (TreeNode)getModel().getRoot();
297             expandAll(new TreePath(root), expand);
298         }
299         
300         private void expandAll(TreePath parent, boolean expand) {
301             // Traverse children
302             TreeNode node = (TreeNode)parent.getLastPathComponent();
303             if (node.getChildCount() >= 0) {
304                 for (Enumeration e=node.children(); e.hasMoreElements(); ) {
305                     TreeNode n = (TreeNode)e.nextElement();
306                     TreePath path = parent.pathByAddingChild(n);
307                     expandAll(path, expand);
308                 }
309             }
310         
311             if (expand) {
312                 expandPath(parent);
313             } else {
314                 collapsePath(parent);
315             }
316         }        
317     }
318     
319     private void loadTreeData(TreeNode rootNode) {
320     	astWidget.setModel(new DefaultTreeModel(rootNode));
321     	astWidget.expandAll(true);
322     }
323     
324     private class ShowListener implements ActionListener {
325         public void actionPerformed(ActionEvent ae) {
326             MyPrintStream ps = new MyPrintStream();
327             System.setOut(ps);
328             TreeNode tn;
329             try {
330                 SimpleNode lastCompilationUnit = getCompilationUnit();
331                 tn = new ASTTreeNode(lastCompilationUnit);
332             } catch (ParseException pe) {            	
333             	tn = new ExceptionNode(pe);
334             	}
335             
336             loadTreeData(tn);
337         }
338     }
339 
340     private class DFAListener implements ActionListener {
341         public void actionPerformed(ActionEvent ae) {
342 
343            DFAGraphRule dfaGraphRule = new DFAGraphRule();
344            RuleSet rs = new RuleSet();
345            SourceType sourceType = getSourceType();
346            if (!sourceType.equals(SourceType.JSP)){
347                rs.addRule(dfaGraphRule);
348            }
349            RuleContext ctx = new RuleContext();
350            ctx.setSourceCodeFilename("[no filename]");
351            StringReader reader = new StringReader(codeEditorPane.getText());
352            PMD pmd = new PMD();
353            pmd.setJavaVersion(sourceType);
354            
355            try {
356                 pmd.processFile(reader, rs, ctx);
357 //           } catch (PMDException pmde) {
358 //               loadTreeData(new ExceptionNode(pmde));
359            } catch (Exception e) {
360                e.printStackTrace();
361            		}
362            
363            List methods = dfaGraphRule.getMethods();
364            if (methods != null && !methods.isEmpty()) {
365                dfaPanel.resetTo(methods, codeEditorPane);
366                dfaPanel.repaint();
367            }
368         }
369     }
370 
371     private class XPathListener implements ActionListener {
372         public void actionPerformed(ActionEvent ae) {
373             xpathResults.clear();
374             if (xpathQueryArea.getText().length() == 0) {
375                 xpathResults.addElement("XPath query field is empty");
376                 xpathResultList.repaint();
377                 codeEditorPane.requestFocus();
378                 return;
379             }
380             SimpleNode c = getCompilationUnit();
381             try {
382                 XPath xpath = new BaseXPath(xpathQueryArea.getText(), new DocumentNavigator());
383                 for (Iterator iter = xpath.selectNodes(c).iterator(); iter.hasNext();) {
384                     StringBuffer sb = new StringBuffer();
385                     Object obj = iter.next();
386                     if (obj instanceof String) {
387                         System.out.println("Result was a string: " + ((String) obj));
388                     } else if (!(obj instanceof Boolean)) {
389                         // if it's a Boolean and it's 'false', what does that mean?
390                         SimpleNode node = (SimpleNode) obj;
391                         String name = node.getClass().getName().substring(node.getClass().getName().lastIndexOf('.') + 1);
392                         String line = " at line " + node.getBeginLine();
393                         sb.append(name).append(line).append(PMD.EOL);
394                         xpathResults.addElement(sb.toString().trim());
395                     }
396                 }
397                 if (xpathResults.isEmpty()) {
398                     xpathResults.addElement("No matching nodes " + System.currentTimeMillis());
399                 }
400             } catch (ParseException pe) {
401                 xpathResults.addElement(pe.fillInStackTrace().getMessage());
402             } catch (JaxenException je) {
403                 xpathResults.addElement(je.fillInStackTrace().getMessage());
404             }
405             xpathResultList.repaint();
406             xpathQueryArea.requestFocus();
407         }
408     }
409 
410     private final CodeEditorTextPane codeEditorPane = new CodeEditorTextPane();
411     private final ASTTreeWidget astWidget			= new ASTTreeWidget(new Vector());
412     private DefaultListModel xpathResults			= new DefaultListModel();
413     private final JList xpathResultList				= new JList(xpathResults);
414     private final JTextArea xpathQueryArea			= new JTextArea(15, 30);
415     private final JFrame frame 						= new JFrame("PMD Rule Designer");
416     private final DFAPanel dfaPanel					= new DFAPanel();
417     private final JRadioButtonMenuItem[] sourceTypeMenuItems = new JRadioButtonMenuItem[sourceTypeSets.length];
418     
419     public Designer() {
420         MatchesFunction.registerSelfInSimpleContext();
421 
422         xpathQueryArea.setFont(new Font("Verdana", Font.PLAIN, 16));
423         makeTextComponentUndoable(codeEditorPane);
424         JSplitPane controlSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(codeEditorPane), createXPathQueryPanel());
425         JSplitPane resultsSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, createASTPanel(), createXPathResultPanel());
426 
427         JTabbedPane tabbed = new JTabbedPane();
428         tabbed.addTab("Abstract Syntax Tree / XPath", resultsSplitPane);
429         tabbed.addTab("Data Flow Analysis", dfaPanel);
430         try {
431             // Remove when minimal runtime support is >= JDK 1.4
432             Method setMnemonicAt = JTabbedPane.class.getMethod("setMnemonicAt", new Class[]{Integer.TYPE, Integer.TYPE});
433             if (setMnemonicAt != null) {
434                 //        // Compatible with >= JDK 1.4
435                 //        tabbed.setMnemonicAt(0, KeyEvent.VK_A);
436                 //        tabbed.setMnemonicAt(1, KeyEvent.VK_D);
437                 setMnemonicAt.invoke(tabbed, new Object[]{NumericConstants.ZERO, new Integer(KeyEvent.VK_A)});
438                 setMnemonicAt.invoke(tabbed, new Object[]{NumericConstants.ONE, new Integer(KeyEvent.VK_D)});
439             }
440         } catch (NoSuchMethodException nsme) { // Runtime is < JDK 1.4
441         } catch (IllegalAccessException e) { // Runtime is >= JDK 1.4 but there was an error accessing the function
442             e.printStackTrace();
443             throw new InternalError("Runtime reports to be >= JDK 1.4 yet String.split(java.lang.String) is broken.");
444         } catch (IllegalArgumentException e) {
445             e.printStackTrace();
446             throw new InternalError("Runtime reports to be >= JDK 1.4 yet String.split(java.lang.String) is broken.");
447         } catch (InvocationTargetException e) { // Runtime is >= JDK 1.4 but there was an error accessing the function
448             e.printStackTrace();
449             throw new InternalError("Runtime reports to be >= JDK 1.4 yet String.split(java.lang.String) is broken.");
450         }
451 
452         JSplitPane containerSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, controlSplitPane, tabbed);
453         containerSplitPane.setContinuousLayout(true);
454 
455         JMenuBar menuBar = createMenuBar();
456         frame.setJMenuBar(menuBar);
457         frame.getContentPane().add(containerSplitPane);
458         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
459 
460         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
461         int screenHeight = screenSize.height;
462         int screenWidth = screenSize.width;
463         
464         frame.pack();
465         frame.setSize((screenWidth*3/4),(screenHeight*3/4));
466         frame.setLocation((screenWidth -frame.getWidth()) / 2, (screenHeight  - frame.getHeight()) / 2);
467         frame.setVisible(true);    
468         resultsSplitPane.setDividerLocation(resultsSplitPane.getMaximumDividerLocation() - (resultsSplitPane.getMaximumDividerLocation() / 2));
469         containerSplitPane.setDividerLocation(containerSplitPane.getMaximumDividerLocation() / 2);
470     }
471 
472     private JMenuBar createMenuBar() {
473         JMenuBar menuBar = new JMenuBar();
474         JMenu menu = new JMenu("JDK");
475         ButtonGroup group = new ButtonGroup();
476                 
477         for (int i=0; i<sourceTypeSets.length; i++) {
478         	JRadioButtonMenuItem button = new JRadioButtonMenuItem(sourceTypeSets[i][0].toString());
479         	sourceTypeMenuItems[i] = button;
480         	group.add(button);
481         	menu.add(button);
482         }
483         sourceTypeMenuItems[defaultSourceTypeSelectionIndex].setSelected(true);
484         menuBar.add(menu);
485 
486         JMenu actionsMenu = new JMenu("Actions");
487         JMenuItem copyXMLItem = new JMenuItem("Copy xml to clipboard");
488         copyXMLItem.addActionListener(new ActionListener() {
489             public void actionPerformed(ActionEvent e) {
490                 copyXmlToClipboard();
491             }
492         });
493         actionsMenu.add(copyXMLItem);
494         JMenuItem createRuleXMLItem = new JMenuItem("Create rule XML");
495         createRuleXMLItem.addActionListener(new ActionListener() {
496             public void actionPerformed(ActionEvent e) {
497                 createRuleXML();
498             }
499         });
500         actionsMenu.add(createRuleXMLItem);
501         menuBar.add(actionsMenu);        
502         
503         return menuBar;
504     }
505 
506     private void createRuleXML() {
507     	CreateXMLRulePanel rulePanel = new CreateXMLRulePanel(xpathQueryArea, codeEditorPane);
508     	JFrame xmlframe = new JFrame("Create XML Rule");
509     	xmlframe.setContentPane(rulePanel);
510     	xmlframe.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
511         xmlframe.setSize(new Dimension(600, 700));
512         xmlframe.addComponentListener(new java.awt.event.ComponentAdapter() {
513         	  public void componentResized(ComponentEvent e) {
514         	    JFrame tmp = (JFrame)e.getSource();
515         	    if (tmp.getWidth()<600 || tmp.getHeight()<700) {
516         	      tmp.setSize(600, 700);
517         	    }
518         	  }
519         	});
520         int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
521         int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;
522         xmlframe.pack();
523         xmlframe.setLocation((screenWidth - xmlframe.getWidth()) / 2, (screenHeight - xmlframe.getHeight()) / 2);
524         xmlframe.setVisible(true);
525     }
526 
527     private JComponent createASTPanel() {
528     	astWidget.setCellRenderer(new ASTCellRenderer());    	
529         return new JScrollPane(astWidget);
530     }
531     
532     private JComponent createXPathResultPanel() {
533         xpathResults.addElement("No results yet");
534         xpathResultList.setBorder(BorderFactory.createLineBorder(Color.black));
535         xpathResultList.setFixedCellWidth(300);
536         JScrollPane scrollPane = new JScrollPane();
537         scrollPane.getViewport().setView(xpathResultList);
538         return scrollPane;
539     }
540 
541     private JPanel createXPathQueryPanel() {
542         JPanel p = new JPanel();
543         p.setLayout(new BorderLayout());
544         xpathQueryArea.setBorder(BorderFactory.createLineBorder(Color.black));
545         makeTextComponentUndoable(xpathQueryArea);
546         JScrollPane scrollPane = new JScrollPane(xpathQueryArea);
547         scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
548         scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
549         final JButton b = createGoButton();
550 
551         p.add(new JLabel("XPath Query (if any)"), BorderLayout.NORTH);
552         p.add(scrollPane, BorderLayout.CENTER);
553         p.add(b, BorderLayout.SOUTH);
554 
555         return p;
556     }
557 
558     private JButton createGoButton() {
559         JButton b = new JButton("Go");
560         b.setMnemonic('g');
561         b.addActionListener(new ShowListener());
562         b.addActionListener(codeEditorPane);
563         b.addActionListener(new XPathListener());
564         b.addActionListener(new DFAListener());
565         return b;
566     }
567 
568     private static void makeTextComponentUndoable(JTextComponent textConponent) {
569         final UndoManager undoManager = new UndoManager();
570         textConponent.getDocument().addUndoableEditListener(new UndoableEditListener() {
571 			     public void undoableEditHappened(
572 			       UndoableEditEvent evt) {
573 			         undoManager.addEdit(evt.getEdit());
574 			     }
575   			 });
576         ActionMap actionMap = textConponent.getActionMap();
577         InputMap inputMap = textConponent.getInputMap();
578         actionMap.put("Undo", new AbstractAction("Undo") {
579 		         public void actionPerformed(ActionEvent evt) {
580 		             try {
581 		                 if (undoManager.canUndo()) {
582 		                     undoManager.undo();
583 		                 }
584 		             } catch (CannotUndoException e) {
585 		             }
586 		         }
587         	 });
588         inputMap.put(KeyStroke.getKeyStroke("control Z"), "Undo");
589           
590         actionMap.put("Redo", new AbstractAction("Redo") {
591 			    public void actionPerformed(ActionEvent evt) {
592 			        try {
593 			            if (undoManager.canRedo()) {
594 			                undoManager.redo();
595 			            } 
596 			        } catch (CannotRedoException e) {
597 			        }
598 			    }
599             });
600         inputMap.put(KeyStroke.getKeyStroke("control Y"), "Redo");
601     }
602 
603     public static void main(String[] args) {
604         new Designer();
605     }
606 
607     private final void copyXmlToClipboard() {
608         if (codeEditorPane.getText() != null && codeEditorPane.getText().trim().length() > 0) {
609             String xml = "";
610             SimpleNode cu = getCompilationUnit();
611             if (cu != null) {
612                 try {
613                     xml = getXmlString(cu);
614                 } catch (IOException e) {
615                     e.printStackTrace();
616                     xml = "Error trying to construct XML representation";
617                 }
618             }
619             Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(xml), this);
620         }
621     }
622 
623     /***
624      * Returns an unformatted xml string (without the declaration)
625      *
626      * @param node
627      * @return String
628      * @throws java.io.IOException
629      */
630     private String getXmlString(SimpleNode node) throws IOException {
631 /*
632         StringWriter writer = new StringWriter();
633         XMLSerializer xmlSerializer = new XMLSerializer(writer, new OutputFormat("XML", "UTF-8", true));
634         xmlSerializer.asDOMSerializer();
635         xmlSerializer.serialize(node.asXml());
636         return writer.toString();
637 */
638         return "FIXME"; // FIXME
639     }
640 
641     public void lostOwnership(Clipboard clipboard, Transferable contents) {
642     }
643 }
644