/**
* 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();
}
}