/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.util.designer; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.io.StringWriter; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.DefaultListModel; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JRadioButtonMenuItem; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; import javax.swing.ScrollPaneConstants; import javax.swing.WindowConstants; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.event.UndoableEditEvent; import javax.swing.event.UndoableEditListener; import javax.swing.text.JTextComponent; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeCellRenderer; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoManager; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.apache.commons.io.IOUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; import org.xml.sax.SAXException; import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RuleSet; import net.sourceforge.pmd.RuleSetFactory; import net.sourceforge.pmd.RuleSets; import net.sourceforge.pmd.SourceCodeProcessor; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.LanguageVersionHandler; import net.sourceforge.pmd.lang.Parser; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.ParseException; import net.sourceforge.pmd.lang.ast.xpath.Attribute; import net.sourceforge.pmd.lang.ast.xpath.AttributeAxisIterator; import net.sourceforge.pmd.lang.dfa.DFAGraphMethod; import net.sourceforge.pmd.lang.dfa.DFAGraphRule; import net.sourceforge.pmd.lang.rule.XPathRule; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; import net.sourceforge.pmd.lang.symboltable.Scope; import net.sourceforge.pmd.lang.symboltable.ScopedNode; import net.sourceforge.pmd.lang.xpath.Initializer; import net.sourceforge.pmd.util.StringUtil; public class Designer implements ClipboardOwner { private boolean exitOnClose = true; private final CodeEditorTextPane codeEditorPane = new CodeEditorTextPane(); private final TreeWidget astTreeWidget = new TreeWidget(new Object[0]); private DefaultListModel xpathResults = new DefaultListModel(); private final JList xpathResultList = new JList(xpathResults); private final JTextArea xpathQueryArea = new JTextArea(15, 30); private final ButtonGroup xpathVersionButtonGroup = new ButtonGroup(); private final TreeWidget symbolTableTreeWidget = new TreeWidget(new Object[0]); private final JFrame frame = new JFrame("PMD Rule Designer (v " + PMD.VERSION + ')'); private final DFAPanel dfaPanel = new DFAPanel(); private final JRadioButtonMenuItem[] languageVersionMenuItems = new JRadioButtonMenuItem[getSupportedLanguageVersions().length]; private static final String SETTINGS_FILE_NAME = System.getProperty("user.home") + System.getProperty("file.separator") + ".pmd_designer.xml"; public Designer(String[] args) { if (args.length > 0) { exitOnClose = !args[0].equals("-noexitonclose"); } Initializer.initialize(); xpathQueryArea.setFont(new Font("Verdana", Font.PLAIN, 16)); JSplitPane controlSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, createCodeEditorPanel(), createXPathQueryPanel()); JSplitPane astAndSymbolTablePane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, createASTPanel(), createSymbolTableResultPanel()); JSplitPane resultsSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, astAndSymbolTablePane, createXPathResultPanel()); JTabbedPane tabbed = new JTabbedPane(); tabbed.addTab("Abstract Syntax Tree / XPath / Symbol Table", resultsSplitPane); tabbed.addTab("Data Flow Analysis", dfaPanel); tabbed.setMnemonicAt(0, KeyEvent.VK_A); tabbed.setMnemonicAt(1, KeyEvent.VK_D); JSplitPane containerSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, controlSplitPane, tabbed); containerSplitPane.setContinuousLayout(true); JMenuBar menuBar = createMenuBar(); frame.setJMenuBar(menuBar); frame.getContentPane().add(containerSplitPane); frame.setDefaultCloseOperation(exitOnClose ? JFrame.EXIT_ON_CLOSE : WindowConstants.DISPOSE_ON_CLOSE); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); int screenHeight = screenSize.height; int screenWidth = screenSize.width; frame.pack(); frame.setSize(screenWidth * 3 / 4, screenHeight * 3 / 4); frame.setLocation((screenWidth - frame.getWidth()) / 2, (screenHeight - frame.getHeight()) / 2); frame.setVisible(true); int horozontalMiddleLocation = controlSplitPane.getMaximumDividerLocation() * 3 / 5; controlSplitPane.setDividerLocation(horozontalMiddleLocation); containerSplitPane.setDividerLocation(containerSplitPane.getMaximumDividerLocation() / 2); astAndSymbolTablePane.setDividerLocation(astAndSymbolTablePane.getMaximumDividerLocation() / 3); resultsSplitPane.setDividerLocation(horozontalMiddleLocation); loadSettings(); } private int getDefaultLanguageVersionSelectionIndex() { return Arrays.asList(getSupportedLanguageVersions()) .indexOf(LanguageRegistry.getLanguage("Java").getDefaultVersion()); } private Node getCompilationUnit() { LanguageVersionHandler languageVersionHandler = getLanguageVersionHandler(); return getCompilationUnit(languageVersionHandler); } static Node getCompilationUnit(LanguageVersionHandler languageVersionHandler, String code) { Parser parser = languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions()); Node node = parser.parse(null, new StringReader(code)); languageVersionHandler.getSymbolFacade().start(node); languageVersionHandler.getTypeResolutionFacade(Designer.class.getClassLoader()).start(node); return node; } private Node getCompilationUnit(LanguageVersionHandler languageVersionHandler) { return getCompilationUnit(languageVersionHandler, codeEditorPane.getText()); } private static LanguageVersion[] getSupportedLanguageVersions() { List<LanguageVersion> languageVersions = new ArrayList<>(); for (LanguageVersion languageVersion : LanguageRegistry.findAllVersions()) { LanguageVersionHandler languageVersionHandler = languageVersion.getLanguageVersionHandler(); if (languageVersionHandler != null) { Parser parser = languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions()); if (parser != null && parser.canParse()) { languageVersions.add(languageVersion); } } } return languageVersions.toArray(new LanguageVersion[languageVersions.size()]); } private LanguageVersion getLanguageVersion() { return getSupportedLanguageVersions()[selectedLanguageVersionIndex()]; } private void setLanguageVersion(LanguageVersion languageVersion) { if (languageVersion != null) { LanguageVersion[] versions = getSupportedLanguageVersions(); for (int i = 0; i < versions.length; i++) { LanguageVersion version = versions[i]; if (languageVersion.equals(version)) { languageVersionMenuItems[i].setSelected(true); break; } } } } private int selectedLanguageVersionIndex() { for (int i = 0; i < languageVersionMenuItems.length; i++) { if (languageVersionMenuItems[i].isSelected()) { return i; } } throw new RuntimeException("Initial default language version not specified"); } private LanguageVersionHandler getLanguageVersionHandler() { LanguageVersion languageVersion = getLanguageVersion(); return languageVersion.getLanguageVersionHandler(); } private class ExceptionNode implements TreeNode { private Object item; private ExceptionNode[] kids; ExceptionNode(Object theItem) { item = theItem; if (item instanceof ParseException) { createKids(); } } // each line in the error message becomes a separate tree node private void createKids() { String message = ((ParseException) item).getMessage(); String[] lines = StringUtil.substringsOf(message, PMD.EOL); kids = new ExceptionNode[lines.length]; for (int i = 0; i < lines.length; i++) { kids[i] = new ExceptionNode(lines[i]); } } @Override public int getChildCount() { return kids == null ? 0 : kids.length; } @Override public boolean getAllowsChildren() { return false; } @Override public boolean isLeaf() { return kids == null; } @Override public TreeNode getParent() { return null; } @Override public TreeNode getChildAt(int childIndex) { return kids[childIndex]; } public String label() { return item.toString(); } @Override public Enumeration<TreeNode> children() { Enumeration<TreeNode> e = new Enumeration<TreeNode>() { int i = 0; @Override public boolean hasMoreElements() { return kids != null && i < kids.length; } @Override public ExceptionNode nextElement() { return kids[i++]; } }; return e; } @Override public int getIndex(TreeNode node) { for (int i = 0; i < kids.length; i++) { if (kids[i] == node) { return i; } } return -1; } } // Tree node that wraps the AST node for the tree widget and // any possible children they may have. private class ASTTreeNode implements TreeNode { private Node node; private ASTTreeNode parent; private ASTTreeNode[] kids; ASTTreeNode(Node theNode) { node = theNode; Node parent = node.jjtGetParent(); if (parent != null) { this.parent = new ASTTreeNode(parent); } } private ASTTreeNode(ASTTreeNode parent, Node theNode) { node = theNode; this.parent = parent; } @Override public int getChildCount() { return node.jjtGetNumChildren(); } @Override public boolean getAllowsChildren() { return false; } @Override public boolean isLeaf() { return node.jjtGetNumChildren() == 0; } @Override public TreeNode getParent() { return parent; } public Scope getScope() { if (node instanceof ScopedNode) { return ((ScopedNode) node).getScope(); } return null; } @Override public Enumeration<TreeNode> children() { if (getChildCount() > 0) { getChildAt(0); // force it to build kids } Enumeration<TreeNode> e = new Enumeration<TreeNode>() { int i = 0; @Override public boolean hasMoreElements() { return kids != null && i < kids.length; } @Override public ASTTreeNode nextElement() { return kids[i++]; } }; return e; } @Override public TreeNode getChildAt(int childIndex) { if (kids == null) { kids = new ASTTreeNode[node.jjtGetNumChildren()]; for (int i = 0; i < kids.length; i++) { kids[i] = new ASTTreeNode(this.parent, node.jjtGetChild(i)); } } return kids[childIndex]; } @Override public int getIndex(TreeNode node) { for (int i = 0; i < kids.length; i++) { if (kids[i] == node) { return i; } } return -1; } public String label() { LanguageVersionHandler languageVersionHandler = getLanguageVersionHandler(); StringWriter writer = new StringWriter(); languageVersionHandler.getDumpFacade(writer, "", false).start(node); return writer.toString(); } public String getToolTipText() { String tooltip = "Line: " + node.getBeginLine() + " Column: " + node.getBeginColumn(); tooltip += " " + label(); return tooltip; } public List<String> getAttributes() { List<String> result = new LinkedList<>(); AttributeAxisIterator attributeAxisIterator = new AttributeAxisIterator(node); while (attributeAxisIterator.hasNext()) { Attribute attribute = attributeAxisIterator.next(); result.add(attribute.getName() + "=" + attribute.getStringValue()); } return result; } } private TreeCellRenderer createNoImageTreeCellRenderer() { DefaultTreeCellRenderer treeCellRenderer = new DefaultTreeCellRenderer(); treeCellRenderer.setLeafIcon(null); treeCellRenderer.setOpenIcon(null); treeCellRenderer.setClosedIcon(null); return treeCellRenderer; } // Special tree variant that knows how to retrieve node labels and // provides the ability to expand all nodes at once. private class TreeWidget extends JTree { private static final long serialVersionUID = 1L; TreeWidget(Object[] items) { super(items); setToolTipText(""); } @Override public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { if (value == null) { return ""; } if (value instanceof ASTTreeNode) { return ((ASTTreeNode) value).label(); } if (value instanceof ExceptionNode) { return ((ExceptionNode) value).label(); } return value.toString(); } @Override public String getToolTipText(MouseEvent e) { if (getRowForLocation(e.getX(), e.getY()) == -1) { return null; } TreePath curPath = getPathForLocation(e.getX(), e.getY()); if (curPath.getLastPathComponent() instanceof ASTTreeNode) { return ((ASTTreeNode) curPath.getLastPathComponent()).getToolTipText(); } else { return super.getToolTipText(e); } } public void expandAll(boolean expand) { TreeNode root = (TreeNode) getModel().getRoot(); expandAll(new TreePath(root), expand); } private void expandAll(TreePath parent, boolean expand) { // Traverse children TreeNode node = (TreeNode) parent.getLastPathComponent(); if (node.getChildCount() >= 0) { for (Enumeration<? extends TreeNode> e = node.children(); e.hasMoreElements();) { TreeNode n = e.nextElement(); TreePath path = parent.pathByAddingChild(n); expandAll(path, expand); } } if (expand) { expandPath(parent); } else { collapsePath(parent); } } } private void loadASTTreeData(TreeNode rootNode) { astTreeWidget.setModel(new DefaultTreeModel(rootNode)); astTreeWidget.setRootVisible(true); astTreeWidget.expandAll(true); } private void loadSymbolTableTreeData(TreeNode rootNode) { if (rootNode != null) { symbolTableTreeWidget.setModel(new DefaultTreeModel(rootNode)); symbolTableTreeWidget.expandAll(true); } else { symbolTableTreeWidget.setModel(null); } } private class ShowListener implements ActionListener { @Override public void actionPerformed(ActionEvent ae) { TreeNode tn; try { Node lastCompilationUnit = getCompilationUnit(); tn = new ASTTreeNode(lastCompilationUnit); } catch (ParseException pe) { tn = new ExceptionNode(pe); } loadASTTreeData(tn); loadSymbolTableTreeData(null); } } private class DFAListener implements ActionListener { @Override public void actionPerformed(ActionEvent ae) { LanguageVersion languageVersion = getLanguageVersion(); DFAGraphRule dfaGraphRule = languageVersion.getLanguageVersionHandler().getDFAGraphRule(); if (dfaGraphRule != null) { final RuleSet rs = new RuleSetFactory().createSingleRuleRuleSet(dfaGraphRule); RuleContext ctx = new RuleContext(); ctx.setSourceCodeFilename("[no filename]." + languageVersion.getLanguage().getExtensions().get(0)); StringReader reader = new StringReader(codeEditorPane.getText()); PMDConfiguration config = new PMDConfiguration(); config.setDefaultLanguageVersion(languageVersion); try { new SourceCodeProcessor(config).processSourceCode(reader, new RuleSets(rs), ctx); // } catch (PMDException pmde) { // loadTreeData(new ExceptionNode(pmde)); } catch (Exception e) { e.printStackTrace(); } List<DFAGraphMethod> methods = dfaGraphRule.getMethods(); if (methods != null && !methods.isEmpty()) { dfaPanel.resetTo(methods, codeEditorPane); dfaPanel.repaint(); } } } } private class XPathListener implements ActionListener { @Override public void actionPerformed(ActionEvent ae) { xpathResults.clear(); if (StringUtil.isEmpty(xpathQueryArea.getText())) { xpathResults.addElement("XPath query field is empty."); xpathResultList.repaint(); codeEditorPane.requestFocus(); return; } Node c = getCompilationUnit(); try { XPathRule xpathRule = new XPathRule() { @Override public void addViolation(Object data, Node node, String arg) { xpathResults.addElement(node); } }; xpathRule.setMessage(""); xpathRule.setLanguage(getLanguageVersion().getLanguage()); xpathRule.setXPath(xpathQueryArea.getText()); xpathRule.setVersion(xpathVersionButtonGroup.getSelection().getActionCommand()); final RuleSet ruleSet = new RuleSetFactory().createSingleRuleRuleSet(xpathRule); RuleSets ruleSets = new RuleSets(ruleSet); RuleContext ruleContext = new RuleContext(); ruleContext.setLanguageVersion(getLanguageVersion()); List<Node> nodes = new ArrayList<>(); nodes.add(c); ruleSets.apply(nodes, ruleContext, xpathRule.getLanguage()); if (xpathResults.isEmpty()) { xpathResults.addElement("No matching nodes " + System.currentTimeMillis()); } } catch (ParseException pe) { xpathResults.addElement(pe.fillInStackTrace().getMessage()); } xpathResultList.repaint(); xpathQueryArea.requestFocus(); } } private class SymbolTableListener implements TreeSelectionListener { @Override public void valueChanged(TreeSelectionEvent e) { if (e.getNewLeadSelectionPath() != null) { ASTTreeNode astTreeNode = (ASTTreeNode) e.getNewLeadSelectionPath().getLastPathComponent(); DefaultMutableTreeNode symbolTableTreeNode = new DefaultMutableTreeNode(); DefaultMutableTreeNode selectedAstTreeNode = new DefaultMutableTreeNode( "AST Node: " + astTreeNode.label()); symbolTableTreeNode.add(selectedAstTreeNode); List<Scope> scopes = new ArrayList<>(); Scope scope = astTreeNode.getScope(); while (scope != null) { scopes.add(scope); scope = scope.getParent(); } Collections.reverse(scopes); for (int i = 0; i < scopes.size(); i++) { scope = scopes.get(i); DefaultMutableTreeNode scopeTreeNode = new DefaultMutableTreeNode( "Scope: " + scope.getClass().getSimpleName()); selectedAstTreeNode.add(scopeTreeNode); for (Map.Entry<NameDeclaration, List<NameOccurrence>> entry : scope.getDeclarations().entrySet()) { DefaultMutableTreeNode nameDeclarationTreeNode = new DefaultMutableTreeNode( entry.getKey().getClass().getSimpleName() + ": " + entry.getKey()); scopeTreeNode.add(nameDeclarationTreeNode); for (NameOccurrence nameOccurrence : entry.getValue()) { DefaultMutableTreeNode nameOccurranceTreeNode = new DefaultMutableTreeNode( "Name occurrence: " + nameOccurrence); nameDeclarationTreeNode.add(nameOccurranceTreeNode); } } } List<String> attributes = astTreeNode.getAttributes(); DefaultMutableTreeNode attributesNode = new DefaultMutableTreeNode( "Attributes (accessible via XPath):"); selectedAstTreeNode.add(attributesNode); for (String attribute : attributes) { attributesNode.add(new DefaultMutableTreeNode(attribute)); } loadSymbolTableTreeData(symbolTableTreeNode); } } } private class CodeHighlightListener implements TreeSelectionListener { @Override public void valueChanged(TreeSelectionEvent e) { if (e.getNewLeadSelectionPath() != null) { ASTTreeNode selected = (ASTTreeNode) e.getNewLeadSelectionPath().getLastPathComponent(); if (selected != null) { codeEditorPane.select(selected.node); } } } } private class ASTListCellRenderer extends JLabel implements ListCellRenderer { private static final long serialVersionUID = 1L; @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (isSelected) { setBackground(list.getSelectionBackground()); setForeground(list.getSelectionForeground()); } else { setBackground(list.getBackground()); setForeground(list.getForeground()); } String text; if (value instanceof Node) { Node node = (Node) value; StringBuffer sb = new StringBuffer(); String name = node.getClass().getName().substring(node.getClass().getName().lastIndexOf('.') + 1); if (Proxy.isProxyClass(value.getClass())) { name = value.toString(); } sb.append(name).append(" at line ").append(node.getBeginLine()).append(" column ") .append(node.getBeginColumn()).append(PMD.EOL); text = sb.toString(); } else { text = value.toString(); } setText(text); return this; } } private class ASTSelectionListener implements ListSelectionListener { @Override public void valueChanged(ListSelectionEvent e) { ListSelectionModel lsm = (ListSelectionModel) e.getSource(); if (!lsm.isSelectionEmpty()) { Object o = xpathResults.get(lsm.getMinSelectionIndex()); if (o instanceof Node) { codeEditorPane.select((Node) o); } } } } private JMenuBar createMenuBar() { JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("Language"); ButtonGroup group = new ButtonGroup(); LanguageVersion[] languageVersions = getSupportedLanguageVersions(); for (int i = 0; i < languageVersions.length; i++) { LanguageVersion languageVersion = languageVersions[i]; JRadioButtonMenuItem button = new JRadioButtonMenuItem(languageVersion.getShortName()); languageVersionMenuItems[i] = button; group.add(button); menu.add(button); } languageVersionMenuItems[getDefaultLanguageVersionSelectionIndex()].setSelected(true); menuBar.add(menu); JMenu actionsMenu = new JMenu("Actions"); JMenuItem copyXMLItem = new JMenuItem("Copy xml to clipboard"); copyXMLItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { copyXmlToClipboard(); } }); actionsMenu.add(copyXMLItem); JMenuItem createRuleXMLItem = new JMenuItem("Create rule XML"); createRuleXMLItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { createRuleXML(); } }); actionsMenu.add(createRuleXMLItem); menuBar.add(actionsMenu); return menuBar; } private void createRuleXML() { CreateXMLRulePanel rulePanel = new CreateXMLRulePanel(xpathQueryArea, codeEditorPane); JFrame xmlframe = new JFrame("Create XML Rule"); xmlframe.setContentPane(rulePanel); xmlframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); xmlframe.setSize(new Dimension(600, 700)); xmlframe.addComponentListener(new java.awt.event.ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { JFrame tmp = (JFrame) e.getSource(); if (tmp.getWidth() < 600 || tmp.getHeight() < 700) { tmp.setSize(600, 700); } } }); int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height; int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width; xmlframe.pack(); xmlframe.setLocation((screenWidth - xmlframe.getWidth()) / 2, (screenHeight - xmlframe.getHeight()) / 2); xmlframe.setVisible(true); } private JComponent createCodeEditorPanel() { JPanel p = new JPanel(); p.setLayout(new BorderLayout()); codeEditorPane.setBorder(BorderFactory.createLineBorder(Color.black)); makeTextComponentUndoable(codeEditorPane); p.add(new JLabel("Source code:"), BorderLayout.NORTH); p.add(new JScrollPane(codeEditorPane), BorderLayout.CENTER); return p; } private JComponent createASTPanel() { astTreeWidget.setCellRenderer(createNoImageTreeCellRenderer()); TreeSelectionModel model = astTreeWidget.getSelectionModel(); model.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); model.addTreeSelectionListener(new SymbolTableListener()); model.addTreeSelectionListener(new CodeHighlightListener()); return new JScrollPane(astTreeWidget); } private JComponent createXPathResultPanel() { xpathResults.addElement("No XPath results yet, run an XPath Query first."); xpathResultList.setBorder(BorderFactory.createLineBorder(Color.black)); xpathResultList.setFixedCellWidth(300); xpathResultList.setCellRenderer(new ASTListCellRenderer()); xpathResultList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); xpathResultList.getSelectionModel().addListSelectionListener(new ASTSelectionListener()); JScrollPane scrollPane = new JScrollPane(); scrollPane.getViewport().setView(xpathResultList); return scrollPane; } private JPanel createXPathQueryPanel() { JPanel p = new JPanel(); p.setLayout(new BorderLayout()); xpathQueryArea.setBorder(BorderFactory.createLineBorder(Color.black)); makeTextComponentUndoable(xpathQueryArea); JScrollPane scrollPane = new JScrollPane(xpathQueryArea); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); final JButton b = createGoButton(); JPanel topPanel = new JPanel(); topPanel.setLayout(new BorderLayout()); topPanel.add(new JLabel("XPath Query (if any):"), BorderLayout.WEST); topPanel.add(createXPathVersionPanel(), BorderLayout.EAST); p.add(topPanel, BorderLayout.NORTH); p.add(scrollPane, BorderLayout.CENTER); p.add(b, BorderLayout.SOUTH); return p; } private JComponent createSymbolTableResultPanel() { symbolTableTreeWidget.setCellRenderer(createNoImageTreeCellRenderer()); return new JScrollPane(symbolTableTreeWidget); } private JPanel createXPathVersionPanel() { JPanel p = new JPanel(); p.add(new JLabel("XPath Version:")); for (Object[] values : XPathRule.VERSION_DESCRIPTOR.choices()) { JRadioButton b = new JRadioButton(); b.setText((String) values[0]); b.setActionCommand(b.getText()); if (values[0].equals(XPathRule.VERSION_DESCRIPTOR.defaultValue())) { b.setSelected(true); } xpathVersionButtonGroup.add(b); p.add(b); } return p; } private JButton createGoButton() { JButton b = new JButton("Go"); b.setMnemonic('g'); b.addActionListener(new ShowListener()); b.addActionListener(new XPathListener()); b.addActionListener(new DFAListener()); b.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { saveSettings(); } }); return b; } private static void makeTextComponentUndoable(JTextComponent textConponent) { final UndoManager undoManager = new UndoManager(); textConponent.getDocument().addUndoableEditListener(new UndoableEditListener() { @Override public void undoableEditHappened(UndoableEditEvent evt) { undoManager.addEdit(evt.getEdit()); } }); ActionMap actionMap = textConponent.getActionMap(); InputMap inputMap = textConponent.getInputMap(); actionMap.put("Undo", new AbstractAction("Undo") { @Override public void actionPerformed(ActionEvent evt) { try { if (undoManager.canUndo()) { undoManager.undo(); } } catch (CannotUndoException e) { } } }); inputMap.put(KeyStroke.getKeyStroke("control Z"), "Undo"); actionMap.put("Redo", new AbstractAction("Redo") { @Override public void actionPerformed(ActionEvent evt) { try { if (undoManager.canRedo()) { undoManager.redo(); } } catch (CannotRedoException e) { } } }); inputMap.put(KeyStroke.getKeyStroke("control Y"), "Redo"); } public static void main(String[] args) { new Designer(args); } final void setCodeEditPaneText(String text) { codeEditorPane.setText(text); } private String getXmlTreeCode() { if (codeEditorPane.getText() != null && codeEditorPane.getText().trim().length() > 0) { Node cu = getCompilationUnit(); return getXmlTreeCode(cu); } return null; } static final String getXmlTreeCode(Node cu) { String xml = null; if (cu != null) { try { xml = getXmlString(cu); } catch (TransformerException e) { e.printStackTrace(); xml = "Error trying to construct XML representation"; } } return xml; } private void copyXmlToClipboard() { String xml = getXmlTreeCode(); if (xml != null) { Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(xml), this); } } /** * Returns an unformatted xml string (without the declaration) * * @throws TransformerException * if the XML cannot be converted to a string */ private static String getXmlString(Node node) throws TransformerException { StringWriter writer = new StringWriter(); Source source = new DOMSource(node.getAsDocument()); Result result = new StreamResult(writer); TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer xformer = transformerFactory.newTransformer(); xformer.setOutputProperty(OutputKeys.INDENT, "yes"); xformer.transform(source, result); return writer.toString(); } @Override public void lostOwnership(Clipboard clipboard, Transferable contents) { } private void loadSettings() { InputStream stream = null; try { File file = new File(SETTINGS_FILE_NAME); if (file.exists()) { DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); stream = new FileInputStream(file); Document document = builder.parse(stream); Element settingsElement = document.getDocumentElement(); Element codeElement = (Element) settingsElement.getElementsByTagName("code").item(0); Element xpathElement = (Element) settingsElement.getElementsByTagName("xpath").item(0); String code = getTextContext(codeElement); String languageVersion = codeElement.getAttribute("language-version"); String xpath = getTextContext(xpathElement); String xpathVersion = xpathElement.getAttribute("version"); codeEditorPane.setText(code); setLanguageVersion(LanguageRegistry.findLanguageVersionByTerseName(languageVersion)); xpathQueryArea.setText(xpath); for (Enumeration<AbstractButton> e = xpathVersionButtonGroup.getElements(); e.hasMoreElements();) { AbstractButton button = e.nextElement(); if (xpathVersion.equals(button.getActionCommand())) { button.setSelected(true); break; } } } } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(stream); } } private void saveSettings() { try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.newDocument(); Element settingsElement = document.createElement("settings"); document.appendChild(settingsElement); Element codeElement = document.createElement("code"); settingsElement.appendChild(codeElement); codeElement.setAttribute("language-version", getLanguageVersion().getTerseName()); codeElement.appendChild(document.createCDATASection(codeEditorPane.getText())); Element xpathElement = document.createElement("xpath"); settingsElement.appendChild(xpathElement); xpathElement.setAttribute("version", xpathVersionButtonGroup.getSelection().getActionCommand()); xpathElement.appendChild(document.createCDATASection(xpathQueryArea.getText())); TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); // This is as close to pretty printing as we'll get using standard // Java APIs. transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); Source source = new DOMSource(document); Result result = new StreamResult(new FileWriter(new File(SETTINGS_FILE_NAME))); transformer.transform(source, result); } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (TransformerException e) { e.printStackTrace(); } } private String getTextContext(Element element) { StringBuilder buf = new StringBuilder(); for (int i = 0; i < element.getChildNodes().getLength(); i++) { org.w3c.dom.Node child = element.getChildNodes().item(i); if (child instanceof Text) { buf.append(((Text) child).getData()); } } return buf.toString(); } }