package net.sf.taverna.t2.activities.xpath.ui.config; | |
import java.awt.AWTEvent; | |
import java.awt.BorderLayout; | |
import java.awt.Color; | |
import java.awt.Component; | |
import java.awt.Cursor; | |
import java.awt.Dimension; | |
import java.awt.GridBagConstraints; | |
import java.awt.GridBagLayout; | |
import java.awt.GridLayout; | |
import java.awt.Insets; | |
import java.awt.Point; | |
import java.awt.Toolkit; | |
import java.awt.event.AWTEventListener; | |
import java.awt.event.ActionEvent; | |
import java.awt.event.ActionListener; | |
import java.awt.event.FocusEvent; | |
import java.awt.event.FocusListener; | |
import java.awt.event.KeyAdapter; | |
import java.awt.event.KeyEvent; | |
import java.awt.event.KeyListener; | |
import java.awt.event.MouseAdapter; | |
import java.awt.event.MouseEvent; | |
import java.awt.geom.Area; | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.IOException; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.prefs.Preferences; | |
import javax.swing.AbstractAction; | |
import javax.swing.BorderFactory; | |
import javax.swing.BoxLayout; | |
import javax.swing.JButton; | |
import javax.swing.JCheckBoxMenuItem; | |
import javax.swing.JFileChooser; | |
import javax.swing.JFrame; | |
import javax.swing.JLabel; | |
import javax.swing.JOptionPane; | |
import javax.swing.JPanel; | |
import javax.swing.JScrollPane; | |
import javax.swing.JSeparator; | |
import javax.swing.JTabbedPane; | |
import javax.swing.JTable; | |
import javax.swing.JTextArea; | |
import javax.swing.JTextField; | |
import javax.swing.JToggleButton; | |
import javax.swing.ListSelectionModel; | |
import javax.swing.Popup; | |
import javax.swing.PopupFactory; | |
import javax.swing.SwingUtilities; | |
import javax.swing.Timer; | |
import javax.swing.event.CaretEvent; | |
import javax.swing.event.CaretListener; | |
import javax.swing.filechooser.FileFilter; | |
import javax.swing.table.DefaultTableModel; | |
import net.sf.taverna.t2.activities.xpath.XPathActivityConfigurationBean; | |
import net.sf.taverna.t2.activities.xpath.ui.config.xmltree.TableCellListener; | |
import net.sf.taverna.t2.activities.xpath.ui.config.xmltree.XPathActivityXMLTree; | |
import net.sf.taverna.t2.activities.xpath.ui.servicedescription.XPathActivityIcon; | |
import net.sf.taverna.t2.workbench.icons.WorkbenchIcons; | |
import org.apache.log4j.Logger; | |
import org.dom4j.Document; | |
import org.dom4j.DocumentException; | |
import org.dom4j.DocumentHelper; | |
import org.dom4j.InvalidXPathException; | |
import org.dom4j.Node; | |
import org.dom4j.XPath; | |
import org.dom4j.XPathException; | |
/** | |
* @author Sergejs Aleksejevs | |
*/ | |
@SuppressWarnings("serial") | |
public class XPathActivityConfigurationPanel extends JPanel { | |
private Logger logger = Logger.getLogger(XPathActivityConfigurationPanel.class); | |
// --- CONSTANTS --- | |
public static final int MAX_NUMBER_OF_MATCHING_NODES_TO_HIGHLIGHT_IN_THE_TREE = 100; | |
private static final Color INACTIVE_PANEL_BACKGROUND_COLOR = new Color(215, | |
215, 215); | |
private static final String EXAMPLE_XML_PROMPT = "Paste example XML here..."; | |
private static final String XPATH_XML_DOCUMENT_DIR_PROPERTY="XPathXMLDocumentDir"; | |
private XPathActivityConfigurationPanel thisPanel; | |
// --- COMPONENTS FOR ACTIVITY CONFIGURATION PANEL --- | |
private JPanel jpActivityConfiguration; | |
private JPanel jpLeft; | |
private JPanel jpRight; | |
private JToggleButton bShowXMLTreeSettings; | |
private Popup xmlTreeSettingsMenu; | |
private long xmlTreeSettingsMenuLastShownAt; | |
private JButton bGenerateXPathExpression; | |
private JPanel jpXMLTreeSettingsMenuContents; | |
private JCheckBoxMenuItem miIncludeAttributes; | |
private JCheckBoxMenuItem miIncludeValues; | |
private JCheckBoxMenuItem miIncludeNamespaces; | |
private JTextArea taSourceXML; | |
private JButton bLoadXMLDocument; | |
private JButton bParseXML; | |
private XPathActivityXMLTree xmlTree; | |
private JScrollPane spXMLTreePlaceholder; | |
// --- COMPONENTS FOR XPATH EDITING PANEL --- | |
private JLabel jlXPathExpressionStatus; | |
private JLabel jlXPathExpression; | |
private JTextField tfXPathExpression; | |
private Map<String, String> xpathNamespaceMap; | |
private JButton bRunXPath; | |
private JLabel jlShowHideNamespaceMappings; | |
private JTable jtXPathNamespaceMappings; | |
private JButton bAddMapping; | |
private JButton bRemoveMapping; | |
private JPanel jpNamespaceMappingsWithButton; | |
// --- COMPONENTS FOR XPATH TESTING PANEL --- | |
private JPanel jpXPathTesting; | |
private JTextField tfExecutedXPathExpression; | |
private JTextField tfMatchingElementCount; | |
private JTabbedPane tpExecutedXPathExpressionResults; | |
private JTextArea taExecutedXPathExpressionResultsAsText; | |
private JScrollPane spExecutedXPathExpressionResultsAsText; | |
private JTextArea taExecutedXPathExpressionResultsAsXML; | |
private JScrollPane spExecutedXPathExpressionResultsAsXML; | |
public XPathActivityConfigurationPanel() { | |
this.thisPanel = this; | |
this.setLayout(new GridBagLayout()); | |
GridBagConstraints c = new GridBagConstraints(); | |
c.gridx = 0; | |
c.gridy = 0; | |
c.fill = GridBagConstraints.BOTH; | |
c.weightx = 1.0; | |
c.weighty = 0.50; | |
c.insets = new Insets(0, 10, 10, 10); | |
this.jpActivityConfiguration = createActivityConfigurationPanel(); | |
this.add(this.jpActivityConfiguration, c); | |
c.gridy++; | |
c.fill = GridBagConstraints.HORIZONTAL; | |
c.weighty = 0; | |
c.insets = new Insets(0, 10, 0, 10); | |
this.add(new JSeparator(), c); | |
// XPath expression editing panel | |
c.gridy++; | |
c.fill = GridBagConstraints.BOTH; | |
c.weighty = 0.05; | |
c.insets = new Insets(5, 10, 5, 10); | |
this.add(createXPathExpressionEditingPanel(), c); | |
c.gridy++; | |
; | |
c.fill = GridBagConstraints.HORIZONTAL; | |
c.weighty = 0; | |
c.insets = new Insets(0, 10, 0, 10); | |
this.add(new JSeparator(), c); | |
// XPath expression testing panel | |
c.gridy++; | |
c.fill = GridBagConstraints.BOTH; | |
c.weighty = 0.35; | |
c.insets = new Insets(5, 10, 0, 10); | |
this.jpXPathTesting = createXPathExpressionTestingPanel(); | |
this.add(this.jpXPathTesting, c); | |
} | |
private JPanel createActivityConfigurationPanel() { | |
JPanel jpConfig = new JPanel(new GridBagLayout()); | |
GridBagConstraints c = new GridBagConstraints(); | |
// text area for example XML document | |
c.gridx = 0; | |
c.gridy = 0; | |
c.fill = GridBagConstraints.BOTH; | |
c.weightx = 0.5; | |
c.weighty = 1.0; | |
c.insets = new Insets(5, 0, 0, 5); | |
taSourceXML = new JTextArea(10, 30); | |
taSourceXML | |
.setToolTipText("<html>Use this text area to paste or load an example XML document.<br>" | |
+ "This document can then be parsed by clicking the button<br>" | |
+ "with a green arrow in order to see its tree structure.</html>"); | |
taSourceXML.setText(EXAMPLE_XML_PROMPT); | |
taSourceXML.addFocusListener(new FocusListener() { | |
public void focusGained(FocusEvent e) { | |
taSourceXML.selectAll(); | |
} | |
public void focusLost(FocusEvent e) { /* do nothing */ | |
} | |
}); | |
taSourceXML.addCaretListener(new CaretListener() { | |
public void caretUpdate(CaretEvent e) { | |
// make sure that it is only allowed to "parse example XML" | |
// when something is actually present in the text area | |
bParseXML.setEnabled(taSourceXML.getText().trim().length() > 0 | |
&& !taSourceXML.getText().trim().equals( | |
EXAMPLE_XML_PROMPT)); | |
} | |
}); | |
jpLeft = new JPanel(new GridLayout(1, 1)); | |
jpLeft.add(new JScrollPane(taSourceXML)); | |
jpConfig.add(jpLeft, c); | |
// button to parse example XML document | |
c.gridx++; | |
c.fill = GridBagConstraints.NONE; | |
c.weightx = 0; | |
c.weighty = 0; | |
c.insets = new Insets(0, 0, 0, 0); | |
bParseXML = new JButton( | |
XPathActivityIcon | |
.getIconById(XPathActivityIcon.XPATH_ACTIVITY_CONFIGURATION_PARSE_XML_ICON)); | |
bParseXML | |
.setToolTipText("Parse example XML document and generate its tree structure"); | |
bParseXML.setEnabled(false); | |
bParseXML.addActionListener(new ActionListener() { | |
public void actionPerformed(ActionEvent e) { | |
parseXML(); | |
} | |
}); | |
jpConfig.add(bParseXML, c); | |
// placeholder for XML tree (will be replaced by a real tree when the | |
// parsing is done) | |
c.gridx++; | |
c.fill = GridBagConstraints.BOTH; | |
c.weightx = 0.5; | |
c.weighty = 1.0; | |
c.insets = new Insets(5, 5, 0, 0); | |
JTextArea taXMLTreePlaceholder = new JTextArea(10, 30); | |
taXMLTreePlaceholder | |
.setToolTipText("<html>This area will show tree structure of the example XML after you<br>" | |
+ "paste it into the space on the left-hand side and press 'Parse'<br>" | |
+ "button with the green arrow.</html>"); | |
taXMLTreePlaceholder.setEditable(false); | |
taXMLTreePlaceholder.setBackground(INACTIVE_PANEL_BACKGROUND_COLOR); | |
spXMLTreePlaceholder = new JScrollPane(taXMLTreePlaceholder); | |
jpRight = new JPanel(new GridLayout(1, 1)); | |
jpRight.add(spXMLTreePlaceholder); | |
jpConfig.add(jpRight, c); | |
// Button to load XML document from a file | |
bLoadXMLDocument = new JButton("Load XML from file", WorkbenchIcons.openIcon); | |
bLoadXMLDocument.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
JFileChooser fileChooser = new JFileChooser(); | |
Preferences prefs = Preferences.userNodeForPackage(getClass()); | |
String curDir = prefs.get(XPATH_XML_DOCUMENT_DIR_PROPERTY, System.getProperty("user.home")); | |
fileChooser.setDialogTitle("Select file to load XML from"); | |
fileChooser.setFileFilter(new FileFilter() { | |
public boolean accept(File f) { | |
return f.isDirectory() || f.getName().toLowerCase().endsWith(".xml"); | |
} | |
public String getDescription() { | |
return ".xml files"; | |
} | |
}); | |
fileChooser.setCurrentDirectory(new File(curDir)); | |
int returnVal = fileChooser.showOpenDialog(((JButton) e | |
.getSource()).getParent()); | |
if (returnVal == JFileChooser.APPROVE_OPTION) { | |
prefs.put(XPATH_XML_DOCUMENT_DIR_PROPERTY, fileChooser | |
.getCurrentDirectory().toString()); | |
File file = fileChooser.getSelectedFile(); | |
// Read the contents of a file into a string | |
// and set the value of the XML document text area to it | |
FileInputStream fis = null; | |
try{ | |
byte[] fileBytes = new byte[(int)file.length()]; | |
fis = new FileInputStream(file); | |
fis.read(fileBytes); | |
String xmlDocument = new String(fileBytes, "UTF-8"); | |
setSourceXML(xmlDocument); | |
} | |
catch(Exception ex){ | |
logger.error("An error occured while trying to read the XML document from file " + file.getAbsolutePath(), ex); | |
JOptionPane.showMessageDialog( | |
((JButton) e.getSource()).getParent(), | |
"There was an error while trying to read the file", | |
"XPath Activity", | |
JOptionPane.ERROR_MESSAGE); | |
} | |
finally{ | |
try { | |
fis.close(); | |
} catch (IOException e1) { | |
// Ignore | |
} | |
} | |
} | |
} | |
}); | |
c.gridx = 0; | |
c.gridy++; | |
c.gridwidth = 1; | |
c.fill = GridBagConstraints.NONE; | |
c.weightx = 0; | |
c.weighty = 0; | |
c.insets = new Insets(5, 0, 0, 5); | |
c.anchor = GridBagConstraints.EAST; | |
jpConfig.add(bLoadXMLDocument, c); | |
// settings for the view of XML tree from example XML document | |
miIncludeAttributes = new JCheckBoxMenuItem("Show XML node attributes"); | |
miIncludeAttributes.setSelected(true); | |
miIncludeAttributes.addActionListener(new ActionListener() { | |
public void actionPerformed(ActionEvent e) { | |
refreshXMLTreeUI(); | |
} | |
}); | |
miIncludeValues = new JCheckBoxMenuItem( | |
"Show values of XML elements and attributes"); | |
miIncludeValues.setSelected(true); | |
miIncludeValues.addActionListener(new ActionListener() { | |
public void actionPerformed(ActionEvent e) { | |
refreshXMLTreeUI(); | |
} | |
}); | |
miIncludeNamespaces = new JCheckBoxMenuItem( | |
"Show namespaces of XML elements"); | |
miIncludeNamespaces.addActionListener(new ActionListener() { | |
public void actionPerformed(ActionEvent e) { | |
refreshXMLTreeUI(); | |
} | |
}); | |
jpXMLTreeSettingsMenuContents = new JPanel(); | |
jpXMLTreeSettingsMenuContents.setBorder(BorderFactory | |
.createRaisedBevelBorder()); | |
jpXMLTreeSettingsMenuContents.setLayout(new BoxLayout( | |
jpXMLTreeSettingsMenuContents, BoxLayout.Y_AXIS)); | |
jpXMLTreeSettingsMenuContents.add(miIncludeAttributes); | |
jpXMLTreeSettingsMenuContents.add(miIncludeValues); | |
jpXMLTreeSettingsMenuContents.add(miIncludeNamespaces); | |
bShowXMLTreeSettings = new JToggleButton("Show XML tree settings...", | |
XPathActivityIcon.getIconById(XPathActivityIcon.UNFOLD_ICON)); | |
bShowXMLTreeSettings.setSelectedIcon(XPathActivityIcon | |
.getIconById(XPathActivityIcon.FOLD_ICON)); | |
bShowXMLTreeSettings.setEnabled(false); | |
bShowXMLTreeSettings.addActionListener(new ActionListener() { | |
public void actionPerformed(ActionEvent e) { | |
if (xmlTreeSettingsMenu == null) { | |
xmlTreeSettingsMenuLastShownAt = System.currentTimeMillis(); | |
Point parentPosition = bShowXMLTreeSettings | |
.getLocationOnScreen(); | |
xmlTreeSettingsMenu = PopupFactory.getSharedInstance() | |
.getPopup( | |
bShowXMLTreeSettings, | |
jpXMLTreeSettingsMenuContents, | |
parentPosition.x, | |
parentPosition.y | |
+ bShowXMLTreeSettings.getHeight()); | |
xmlTreeSettingsMenu.show(); | |
} else { | |
bShowXMLTreeSettings.setSelected(false); | |
} | |
} | |
}); | |
bGenerateXPathExpression = new JButton("Generate XPath expression", | |
XPathActivityIcon | |
.getIconById(XPathActivityIcon.XML_TREE_NODE_ICON)); | |
bGenerateXPathExpression.addActionListener(new ActionListener() { | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
updateXPathEditingPanelValues(); | |
} | |
}); | |
JPanel xmlTreeButtonPanel = new JPanel(); | |
xmlTreeButtonPanel.add(bGenerateXPathExpression); | |
xmlTreeButtonPanel.add(bShowXMLTreeSettings); | |
c.gridx = 2; | |
c.gridwidth = 1; | |
c.fill = GridBagConstraints.NONE; | |
c.weightx = 0; | |
c.weighty = 0; | |
c.insets = new Insets(5, 0, 0, 0); | |
c.anchor = GridBagConstraints.EAST; | |
jpConfig.add(xmlTreeButtonPanel, c); | |
// register a new listener for all AWT mouse events - this will be used | |
// to identify clicks outside of the XML tree popup menu and the toggle | |
// button used to show/hide it | |
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() { | |
public void eventDispatched(AWTEvent event) { | |
if (event instanceof MouseEvent && xmlTreeSettingsMenu != null) { | |
MouseEvent e = (MouseEvent) event; | |
if (e.getClickCount() > 0 | |
&& (e.getWhen() - xmlTreeSettingsMenuLastShownAt) > 100) { | |
// convert a point where mouse click was made from | |
// relative coordinates of the source component | |
// to the coordinates of the panel that represents the | |
// contents of the popup menu | |
Point clickRelativeToOverlay = SwingUtilities | |
.convertPoint((Component) e.getSource(), e | |
.getPoint(), | |
jpXMLTreeSettingsMenuContents); | |
Area areaOfPopupPanelAndToggleButton = new Area( | |
jpXMLTreeSettingsMenuContents.getBounds()); | |
// only hide the popup menu if a click was made outside | |
// of the calculated area -- | |
// plus not on one of the associated toggle buttons | |
if (!areaOfPopupPanelAndToggleButton | |
.contains(clickRelativeToOverlay)) { | |
xmlTreeSettingsMenu.hide(); | |
bShowXMLTreeSettings.setSelected(false); | |
// if the popup menu was dismissed by a click on the | |
// toggle button that | |
// has made it visible, this timer makes sure that | |
// this click doesn't | |
// re-show the popup menu | |
new Timer(100, new ActionListener() { | |
public void actionPerformed(ActionEvent e) { | |
((Timer) e.getSource()).stop(); | |
xmlTreeSettingsMenu = null; | |
} | |
}).start(); | |
} | |
} | |
} | |
} | |
}, AWTEvent.MOUSE_EVENT_MASK); | |
return (jpConfig); | |
} | |
private JPanel createXPathExpressionEditingPanel() { | |
this.jlXPathExpressionStatus = new JLabel(); | |
this.jlXPathExpression = new JLabel("XPath expression"); | |
this.bRunXPath = new JButton("Run XPath"); | |
this.bRunXPath.setEnabled(false); | |
this.bRunXPath.addActionListener(new ActionListener() { | |
public void actionPerformed(ActionEvent e) { | |
runXPath(true); | |
} | |
}); | |
this.tfXPathExpression = new JTextField(30); | |
this.tfXPathExpression.setPreferredSize(new Dimension(0, this.bRunXPath | |
.getPreferredSize().height)); | |
this.tfXPathExpression.setMinimumSize(new Dimension(0, this.bRunXPath | |
.getPreferredSize().height)); | |
this.tfXPathExpression.addCaretListener(new CaretListener() { | |
public void caretUpdate(CaretEvent e) { | |
validateXPathAndUpdateUI(); | |
} | |
}); | |
this.tfXPathExpression.addKeyListener(new KeyListener() { | |
public void keyPressed(KeyEvent e) { | |
if (e.getKeyCode() == KeyEvent.VK_ENTER) { | |
if (bRunXPath.isEnabled()) { | |
// it is safe to check that ENTER key may execute the | |
// XPath expression if the | |
// "Run XPath" button is enabled, as expression | |
// validation is responsible for | |
// enabling / disabling the button as the expression | |
// changes | |
runXPath(true); | |
} | |
} | |
} | |
public void keyReleased(KeyEvent e) { /* not in use */ | |
} | |
public void keyTyped(KeyEvent e) { /* not in use */ | |
} | |
}); | |
JPanel jpXPath = new JPanel(new GridBagLayout()); | |
GridBagConstraints c = new GridBagConstraints(); | |
c.fill = GridBagConstraints.HORIZONTAL; | |
c.weighty = 0; | |
c.gridx = 0; | |
c.gridy = 0; | |
c.weightx = 0; | |
jpXPath.add(jlXPathExpressionStatus); | |
c.gridx++; | |
c.weightx = 0.0; | |
c.insets = new Insets(0, 10, 0, 0); | |
jpXPath.add(jlXPathExpression, c); | |
c.gridx++; | |
c.weightx = 1.0; | |
c.insets = new Insets(0, 10, 0, 10); | |
jpXPath.add(tfXPathExpression, c); | |
c.gridx++; | |
c.weightx = 0; | |
c.insets = new Insets(0, 0, 0, 0); | |
jpXPath.add(bRunXPath, c); | |
c.gridx = 2; | |
c.gridy++; | |
c.weightx = 1.0; | |
c.weighty = 0; | |
c.gridwidth = 2; | |
c.fill = GridBagConstraints.NONE; | |
c.anchor = GridBagConstraints.WEST; | |
c.insets = new Insets(0, 10, 0, 10); | |
jlShowHideNamespaceMappings = new JLabel("Show namespace mappings..."); | |
jlShowHideNamespaceMappings.setForeground(Color.BLUE); | |
jlShowHideNamespaceMappings.setCursor(new Cursor(Cursor.HAND_CURSOR)); | |
jlShowHideNamespaceMappings.addMouseListener(new MouseAdapter() { | |
public void mouseClicked(MouseEvent e) { | |
jpNamespaceMappingsWithButton | |
.setVisible(!jpNamespaceMappingsWithButton.isVisible()); | |
jlShowHideNamespaceMappings | |
.setText((jpNamespaceMappingsWithButton.isVisible() ? "Hide" | |
: "Show") | |
+ " namespace mappings..."); | |
thisPanel.validate(); | |
} | |
}); | |
jpXPath.add(jlShowHideNamespaceMappings, c); | |
// namespace mapping table | |
DefaultTableModel tableModel = new DefaultTableModel(); | |
tableModel.addColumn("Namespace Prefix"); | |
tableModel.addColumn("Namespace URI"); | |
jtXPathNamespaceMappings = new JTable(); | |
jtXPathNamespaceMappings.setModel(tableModel); | |
// ((DefaultCellEditor)jtXPathNamespaceMappings.getDefaultEditor(String.class)).setClickCountToStart(1); | |
// // TODO - enable if one-click-to-start-editing behaviour is required | |
// TODO - next line is to be enabled when Taverna is migrated to Java | |
// 1.6; for now it's fine to run without this | |
// jtXPathNamespaceMappings.setFillsViewportHeight(true); // makes sure | |
// that when the dedicated area is larger than the table, the latter is | |
// stretched vertically to fill the empty space | |
jtXPathNamespaceMappings | |
.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // only one row can be selected at a time | |
jtXPathNamespaceMappings | |
.setPreferredScrollableViewportSize(new Dimension(200, 50)); // NB! this prevents the table from occupying most of the space in the panel when screen is maximized | |
jtXPathNamespaceMappings.addKeyListener(new KeyAdapter() { | |
public void keyReleased(KeyEvent e) { | |
if (e.getKeyCode() == KeyEvent.VK_DELETE) { | |
removeNamespaceMapping(); | |
} | |
} | |
}); | |
TableCellListener cellListener = new TableCellListener( | |
jtXPathNamespaceMappings, new AbstractAction() { | |
public void actionPerformed(ActionEvent e) { | |
TableCellListener tcl = (TableCellListener) e | |
.getSource(); | |
if (tcl.getColumn() == 0) { | |
// prefix was modified | |
String newPrefix = (String) tcl.getNewValue(); | |
if (xpathNamespaceMap.containsKey(newPrefix)) { | |
// such prefix already exists - change won't be | |
// saved | |
JOptionPane | |
.showMessageDialog( | |
thisPanel, | |
"Cannot update namespace prefix: " | |
+ "updated value already exists", | |
"XPath Activity", | |
JOptionPane.WARNING_MESSAGE); | |
} else { | |
// update the map with the new prefix for the | |
// same URI value | |
String oldPrefix = (String) tcl.getOldValue(); | |
xpathNamespaceMap.put(newPrefix, | |
xpathNamespaceMap.remove(oldPrefix)); | |
} | |
} else { | |
// simple case - just the URI value has changed: | |
// just overwrite the value in the namespace map | |
String prefixOfUpdatedURI = (String) jtXPathNamespaceMappings | |
.getModel().getValueAt(tcl.getRow(), 0); | |
xpathNamespaceMap.put(prefixOfUpdatedURI, | |
(String) tcl.getNewValue()); | |
} | |
// either way - reload from the local map (map could be | |
// not updated if the validation didn't succeed) | |
reloadNamespaceMappingTableFromLocalMap(); | |
} | |
}); | |
jtXPathNamespaceMappings.getColumnModel().getColumn(0) | |
.setPreferredWidth(20); // set relative sizes of columns | |
jtXPathNamespaceMappings.getColumnModel().getColumn(1) | |
.setPreferredWidth(300); | |
JScrollPane spXPathNamespaceMappings = new JScrollPane( | |
jtXPathNamespaceMappings); | |
spXPathNamespaceMappings.setAlignmentY(TOP_ALIGNMENT); | |
spXPathNamespaceMappings.setMinimumSize(new Dimension(200, 50)); // makes the table to have at least two rows visible in all cases - no matter how small the parent panel is | |
bAddMapping = new JButton("Add Mapping"); | |
bAddMapping.addActionListener(new ActionListener() { | |
public void actionPerformed(ActionEvent e) { | |
addNamespaceMapping(); | |
} | |
}); | |
bRemoveMapping = new JButton("Remove Mapping"); | |
bRemoveMapping.setEnabled(false); | |
bRemoveMapping.addActionListener(new ActionListener() { | |
public void actionPerformed(ActionEvent e) { | |
removeNamespaceMapping(); | |
} | |
}); | |
bAddMapping.setMinimumSize(bRemoveMapping.getPreferredSize()); // make sure that the 'Add Mapping' button is of the same size as 'Remove Mapping' | |
bAddMapping.setPreferredSize(bRemoveMapping.getPreferredSize()); // -- both are required to achieve desired behaviour when window is resized / namespace mapping table is enabled/disabled | |
bRunXPath.setMinimumSize(bRemoveMapping.getPreferredSize()); // do the same for 'Run XPath' button | |
bRunXPath.setPreferredSize(bRemoveMapping.getPreferredSize()); | |
JPanel jpAddRemoveButtons = new JPanel(); | |
jpAddRemoveButtons.setLayout(new GridBagLayout()); | |
GridBagConstraints cAddRemove = new GridBagConstraints(); | |
cAddRemove.gridx = 0; | |
cAddRemove.gridy = 0; | |
cAddRemove.weightx = 1.0; | |
cAddRemove.anchor = GridBagConstraints.NORTH; | |
cAddRemove.fill = GridBagConstraints.HORIZONTAL; | |
jpAddRemoveButtons.add(bAddMapping, cAddRemove); | |
cAddRemove.gridy++; | |
cAddRemove.weighty = 1.0; | |
cAddRemove.insets = new Insets(2, 0, 0, 0); | |
jpAddRemoveButtons.add(bRemoveMapping, cAddRemove); | |
jpNamespaceMappingsWithButton = new JPanel(); | |
jpNamespaceMappingsWithButton.setVisible(false); | |
jpNamespaceMappingsWithButton.setLayout(new BorderLayout(10, 0)); | |
jpNamespaceMappingsWithButton.add(spXPathNamespaceMappings, | |
BorderLayout.CENTER); | |
jpNamespaceMappingsWithButton | |
.add(jpAddRemoveButtons, BorderLayout.EAST); | |
c.gridx = 0; | |
c.gridy++; | |
c.gridwidth = 4; | |
c.fill = GridBagConstraints.BOTH; | |
c.weightx = 1.0; | |
c.weighty = 1.0; | |
c.insets = new Insets(5, 0, 0, 0); | |
jpXPath.add(jpNamespaceMappingsWithButton, c); | |
// initialise some values / tooltips | |
resetXPathEditingPanel(); | |
return (jpXPath); | |
} | |
protected void addNamespaceMapping() { | |
TwoFieldQueryPanel queryPanel = new TwoFieldQueryPanel( | |
"Namespace prefix:", "Namespace URI:"); | |
int result = JOptionPane.showConfirmDialog(this, queryPanel, | |
"XPath Activity - Create new namespace mapping", | |
JOptionPane.OK_CANCEL_OPTION); | |
if (result == JOptionPane.OK_OPTION) { | |
boolean bInvalidMapping = true; | |
do { | |
bInvalidMapping = queryPanel.getFirstValue().length() == 0 | |
|| queryPanel.getSecondValue().length() == 0 | |
|| xpathNamespaceMap.containsKey(queryPanel | |
.getFirstValue()); | |
if (bInvalidMapping) { | |
queryPanel = new TwoFieldQueryPanel( | |
"<html><center><font color=\"red\">ERROR: you must " | |
+ "enter values for both namespace prefix and URI. Prefix must be<br>" | |
+ "unique in the mapping table - duplicates are not allowed!</font></center></html>", | |
"Namespace prefix:", queryPanel.getFirstValue(), | |
"Namespace URI:", queryPanel.getSecondValue()); | |
result = JOptionPane.showConfirmDialog(this, queryPanel, | |
"XPath Activity - Create new namespace mapping", | |
JOptionPane.OK_CANCEL_OPTION); | |
} | |
} while (bInvalidMapping && result == JOptionPane.OK_OPTION); | |
if (result == JOptionPane.OK_OPTION && !bInvalidMapping) { | |
// the value appears to be valid and OK was pressed - create new | |
// mapping | |
this.xpathNamespaceMap.put(queryPanel.getFirstValue(), | |
queryPanel.getSecondValue()); | |
reloadNamespaceMappingTableFromLocalMap(); | |
} | |
} | |
} | |
protected void removeNamespaceMapping() { | |
int selectedRow = jtXPathNamespaceMappings.getSelectedRow(); | |
if (selectedRow != -1) { | |
// some row is selected - need to delete it and refresh table's UI | |
// (but first stop editing to avoid | |
// problems with cell editor trying to store an edited value after | |
// edited row has been deleted) | |
if (jtXPathNamespaceMappings.getCellEditor() != null) { | |
jtXPathNamespaceMappings.getCellEditor().stopCellEditing(); | |
} | |
xpathNamespaceMap.remove(jtXPathNamespaceMappings.getValueAt( | |
selectedRow, 0)); | |
reloadNamespaceMappingTableFromLocalMap(); | |
// select another row in the table | |
int rowCount = jtXPathNamespaceMappings.getRowCount(); | |
if (rowCount > 0) { | |
if (selectedRow < jtXPathNamespaceMappings.getRowCount()) { | |
// select the row that followed the one that was deleted | |
jtXPathNamespaceMappings.getSelectionModel() | |
.setSelectionInterval(selectedRow, selectedRow); | |
} else { | |
// last row in the table was deleted - select the one that | |
// is the new last row | |
jtXPathNamespaceMappings.getSelectionModel() | |
.setSelectionInterval(rowCount - 1, rowCount - 1); | |
} | |
} | |
} else { | |
JOptionPane.showMessageDialog(thisPanel, | |
"Please select a mapping to delete in the table first!", | |
"XPath Activity", JOptionPane.WARNING_MESSAGE); | |
} | |
} | |
private JPanel createXPathExpressionTestingPanel() { | |
JPanel jpTesting = new JPanel(new GridBagLayout()); | |
GridBagConstraints c = new GridBagConstraints(); | |
c.gridx = 0; | |
c.gridy = 0; | |
c.gridwidth = 1; | |
c.anchor = GridBagConstraints.WEST; | |
c.fill = GridBagConstraints.NONE; | |
c.weightx = 0; | |
c.weighty = 0; | |
c.insets = new Insets(0, 0, 10, 10); | |
jpTesting.add(new JLabel("Executed XPath expression:"), c); | |
c.gridx++; | |
c.fill = GridBagConstraints.HORIZONTAL; | |
c.weightx = 1.0; | |
c.weighty = 0; | |
c.insets = new Insets(0, 0, 10, 10); | |
tfExecutedXPathExpression = new JTextField(); | |
tfExecutedXPathExpression.setEditable(false); | |
tfExecutedXPathExpression.setBorder(null); | |
jpTesting.add(tfExecutedXPathExpression, c); | |
c.gridx = 0; | |
c.gridy++; | |
c.fill = GridBagConstraints.NONE; | |
c.weightx = 0; | |
c.weighty = 0; | |
c.insets = new Insets(0, 0, 5, 10); | |
jpTesting.add(new JLabel("Number of matching nodes:"), c); | |
c.gridx++; | |
c.fill = GridBagConstraints.HORIZONTAL; | |
c.weightx = 1.0; | |
c.weighty = 0; | |
c.insets = new Insets(0, 0, 5, 10); | |
tfMatchingElementCount = new JTextField(); | |
tfMatchingElementCount.setEditable(false); | |
tfMatchingElementCount.setBorder(null); | |
jpTesting.add(tfMatchingElementCount, c); | |
c.gridx = 0; | |
c.gridy++; | |
c.gridwidth = 2; | |
c.fill = GridBagConstraints.BOTH; | |
c.weightx = 1.0; | |
c.weighty = 1.0; | |
tpExecutedXPathExpressionResults = new JTabbedPane(); | |
jpTesting.add(tpExecutedXPathExpressionResults, c); | |
taExecutedXPathExpressionResultsAsText = new JTextArea(); | |
taExecutedXPathExpressionResultsAsText.setEditable(false); | |
spExecutedXPathExpressionResultsAsText = new JScrollPane( | |
taExecutedXPathExpressionResultsAsText); | |
spExecutedXPathExpressionResultsAsText.setPreferredSize(new Dimension( | |
200, 60)); | |
spExecutedXPathExpressionResultsAsText.setBorder(BorderFactory | |
.createLineBorder(INACTIVE_PANEL_BACKGROUND_COLOR, 3)); | |
tpExecutedXPathExpressionResults.add("Results as text", | |
spExecutedXPathExpressionResultsAsText); | |
taExecutedXPathExpressionResultsAsXML = new JTextArea(); | |
taExecutedXPathExpressionResultsAsXML.setEditable(false); | |
spExecutedXPathExpressionResultsAsXML = new JScrollPane( | |
taExecutedXPathExpressionResultsAsXML); | |
spExecutedXPathExpressionResultsAsXML.setPreferredSize(new Dimension( | |
200, 60)); | |
spExecutedXPathExpressionResultsAsXML.setBorder(BorderFactory | |
.createLineBorder(INACTIVE_PANEL_BACKGROUND_COLOR, 3)); | |
tpExecutedXPathExpressionResults.add("Results as XML", | |
spExecutedXPathExpressionResultsAsXML); | |
// initialise some values / tooltips | |
resetXPathTestingPanel(); | |
return (jpTesting); | |
} | |
protected void parseXML() { | |
String xmlData = taSourceXML.getText(); | |
try { | |
xmlTree = XPathActivityXMLTree.createFromXMLData(xmlData, | |
miIncludeAttributes.isSelected(), miIncludeValues | |
.isSelected(), miIncludeNamespaces.isSelected(), | |
this); | |
xmlTree | |
.setToolTipText("<html>This is a tree structure of the XML document that you have pasted.<br><br>" | |
+ "Clicking on the nodes in this tree will automatically generate a<br>" | |
+ "corresponding XPath expression. Multiple <b>identical</b> nodes can<br>" | |
+ "be selected at once - in this case <b>wildcards</b> will be used in the<br>" | |
+ "generated XPath expression to if selected nodes have different<br>" | |
+ "ancestors. Other nodes that match the generated XPath expression<br>" | |
+ "will also be selected in the tree.<br><br>" | |
+ "Contextual menu provides convenience methods for expanding or<br>" | |
+ "collapsing the tree." + "</html>"); | |
xmlTree.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); | |
JScrollPane spXMLTree = new JScrollPane(xmlTree); | |
spXMLTree.setPreferredSize(spXMLTreePlaceholder.getPreferredSize()); | |
jpRight.removeAll(); | |
jpRight.add(spXMLTree); | |
// all successful - enable options to modify the tree | |
this.bShowXMLTreeSettings.setEnabled(true); | |
// data structures inside the XML tree were reset (as the tree was | |
// re-created) - | |
// now reset the UI to the initial state as well | |
resetXPathEditingPanel(); | |
resetXPathTestingPanel(); | |
// XML tree has pre-populated the namespace map with the namespaces | |
// specified in the | |
// root element of the tree - load these values | |
updateXPathEditingPanelValues(); | |
this.validate(); | |
this.repaint(); | |
} catch (DocumentException e) { | |
JOptionPane.showMessageDialog(this, e.getMessage(), | |
"XPath Activity", JOptionPane.ERROR_MESSAGE); | |
this.taSourceXML.requestFocusInWindow(); | |
return; | |
} | |
} | |
/** | |
* Makes the {@link XPathActivityXMLTree} to refresh its UI from the | |
* original XML document that was used to create it in first place. | |
* | |
* The reason for using this method is to apply new options to the way the | |
* tree is rendered - e.g. attributes shown/hidden in the tree, values and | |
* namespaces shown/hidden, etc. | |
*/ | |
protected void refreshXMLTreeUI() { | |
this.xmlTree.refreshFromExistingDocument(this.miIncludeAttributes | |
.isSelected(), this.miIncludeValues.isSelected(), | |
this.miIncludeNamespaces.isSelected()); | |
} | |
/** | |
* Initialises XPath Editing panel: -- resets XPath expression that is being | |
* shown; -- resets local copy of namespace map; -- resets UI of namespace | |
* mapping table; | |
*/ | |
private void resetXPathEditingPanel() { | |
tfXPathExpression.setText(""); | |
validateXPathAndUpdateUI(); | |
// clear the local copy of namespace map | |
xpathNamespaceMap = new HashMap<String, String>(); | |
// clear the namespace mapping table and reload the data from the map | |
DefaultTableModel tableModel = (DefaultTableModel) jtXPathNamespaceMappings | |
.getModel(); | |
tableModel.getDataVector().removeAllElements(); | |
} | |
/** | |
* Initialises XPath testing panel which shows results of executing current | |
* XPath expression against the example XML - the panel is returned to the | |
* way it looks when it is first loaded. | |
*/ | |
private void resetXPathTestingPanel() { | |
this.tfExecutedXPathExpression.setText("--"); | |
this.tfMatchingElementCount.setText("--"); | |
this.taExecutedXPathExpressionResultsAsText.setText(""); | |
this.taExecutedXPathExpressionResultsAsText | |
.setBackground(INACTIVE_PANEL_BACKGROUND_COLOR); | |
this.taExecutedXPathExpressionResultsAsXML.setText(""); | |
this.taExecutedXPathExpressionResultsAsXML | |
.setBackground(INACTIVE_PANEL_BACKGROUND_COLOR); | |
} | |
public void updateXPathEditingPanelValues() { | |
if (xmlTree.getCurrentXPathExpression() != null) { | |
tfXPathExpression.setText(xmlTree.getCurrentXPathExpression() | |
.getText()); | |
} | |
// clear the local copy of namespace map and update it with all values | |
// from | |
// the map in XML tree instance (which was apparently just re-generated | |
// on user request) | |
xpathNamespaceMap.clear(); | |
xpathNamespaceMap.putAll(xmlTree.getCurrentXPathNamespaces()); | |
// clear the namespace mapping table and reload the data from the map | |
reloadNamespaceMappingTableFromLocalMap(); | |
} | |
protected void reloadNamespaceMappingTableFromLocalMap() { | |
// clear the namespace mapping table and reload the data from the map | |
DefaultTableModel tableModel = (DefaultTableModel) jtXPathNamespaceMappings | |
.getModel(); | |
tableModel.getDataVector().removeAllElements(); | |
for (Map.Entry<String, String> mapping : this.xpathNamespaceMap | |
.entrySet()) { | |
tableModel.addRow(new Object[] { mapping.getKey(), | |
mapping.getValue() }); | |
} | |
bRemoveMapping.setEnabled(this.xpathNamespaceMap.entrySet().size() > 0); | |
repaint(); | |
} | |
private String getXPathValidationErrorMessage() { | |
try { | |
// try to parse the XPath expression... | |
DocumentHelper.createXPath(tfXPathExpression.getText().trim()); | |
// ...success | |
return (""); | |
} catch (InvalidXPathException e) { | |
// ...failed to parse the XPath expression: notify of the error | |
return (e.getMessage()); | |
} | |
} | |
/** | |
* Validates the current XPath expression and updates UI accordingly: -- | |
* XPath status icon is updated; -- tooltip for the icon explains the | |
* status; -- 'Run XPath' button is enabled/disabled depending on validity | |
* of XPath expression and existence of example data in the XML tree | |
*/ | |
protected void validateXPathAndUpdateUI() { | |
String candidatePath = tfXPathExpression.getText(); | |
int xpathStatus = XPathActivityConfigurationBean | |
.validateXPath(candidatePath); | |
switch (xpathStatus) { | |
case XPathActivityConfigurationBean.XPATH_VALID: | |
// success: expression is correct | |
jlXPathExpressionStatus.setIcon(XPathActivityIcon | |
.getIconById(XPathActivityIcon.XPATH_STATUS_OK_ICON)); | |
jlXPathExpressionStatus | |
.setToolTipText("Current XPath expression is well-formed and valid"); | |
// could allow to execute against example XML, with only condition: | |
// XML tree must be populated | |
// (that is, there should be something to run the expression | |
// against) | |
if (xmlTree != null) { | |
this.bRunXPath.setEnabled(true); | |
this.bRunXPath | |
.setToolTipText("<html>Evaluate current XPath expression against the XML document<br>" | |
+ "whose structure is shown in the tree view above.</html>"); | |
} else { | |
this.bRunXPath.setEnabled(false); | |
this.bRunXPath | |
.setToolTipText("<html>No XML document to evaluate the current XPath expression against.<br><br>" | |
+ "Paste some example XML into the area in the top-left section of the<br>" | |
+ "window, then parse it by clicking on the button with the green arrow<br>" | |
+ "in order to test your XPath expression.</html>"); | |
} | |
break; | |
case XPathActivityConfigurationBean.XPATH_EMPTY: | |
// no XPath expression - can't tell if it is correct + nothing to | |
// execute | |
jlXPathExpressionStatus.setIcon(XPathActivityIcon | |
.getIconById(XPathActivityIcon.XPATH_STATUS_UNKNOWN_ICON)); | |
jlXPathExpressionStatus | |
.setToolTipText("<html>There is no XPath expression to validate.<br><br>" | |
+ "<b>Hint:</b> select something in the tree view showing the structure<br>" | |
+ "of the XML document that you have pasted (or type the XPath<br>" | |
+ "expression manually).</html>"); | |
this.bRunXPath.setEnabled(false); | |
this.bRunXPath.setToolTipText("No XPath expression to execute"); | |
break; | |
case XPathActivityConfigurationBean.XPATH_INVALID: | |
// failed to parse the XPath expression: notify of the error | |
jlXPathExpressionStatus.setIcon(XPathActivityIcon | |
.getIconById(XPathActivityIcon.XPATH_STATUS_ERROR_ICON)); | |
jlXPathExpressionStatus | |
.setToolTipText(getXPathValidationErrorMessage()); | |
this.bRunXPath.setEnabled(false); | |
this.bRunXPath | |
.setToolTipText("Cannot execute invalid XPath expression"); | |
break; | |
} | |
} | |
/** | |
* Executes the current XPath expression against the current XML tree. | |
* | |
* @param displayResults | |
* <code>true</code> to execute and display results in the XPath | |
* activity configuration panel (this happens when the 'Run | |
* XPath' button is clicked);<br/> | |
* <false> to run the expression quietly and simply return the | |
* number of matching nodes. | |
* @return Number of nodes in the XML tree that match the current XPath | |
* expression. (Or <code>-1</code> if an error has occurred during | |
* the execution -- error messages will only be shown if | |
* <code>displayResults == true</code>). | |
*/ | |
public int runXPath(boolean displayResults) { | |
// ----- RUNNING THE XPath EXPRESSION ----- | |
XPath expr = null; | |
try { | |
expr = DocumentHelper.createXPath(this.tfXPathExpression.getText()); | |
expr.setNamespaceURIs(this.xpathNamespaceMap); | |
} catch (InvalidXPathException e) { | |
if (displayResults) { | |
JOptionPane | |
.showMessageDialog( | |
thisPanel, | |
"Incorrect XPath Expression\n\n" | |
+ "Please check the expression if you have manually modified it;\n" | |
+ "Alternatively, try to select another node from the XML tree.\n\n" | |
+ "------------------------------------------------------------------------------------\n\n" | |
+ "XPath processing library reported the following error:\n" | |
+ e.getMessage(), "XPath Activity", | |
JOptionPane.ERROR_MESSAGE); | |
} | |
return (-1); | |
} | |
Document doc = xmlTree.getDocumentUsedToPopulateTree(); | |
List<Node> matchingNodes = null; | |
int matchingNodeCount = -1; | |
try { | |
matchingNodes = expr.selectNodes(doc); | |
matchingNodeCount = matchingNodes.size(); | |
} catch (XPathException e) { | |
if (displayResults) { | |
JOptionPane | |
.showMessageDialog( | |
thisPanel, | |
"Unexpected error has occurred while executing the XPath expression.\n\n" | |
+ "If you have manually modified the XPath expression and/or namespace mappings,\n" | |
+ "please check you changes. Alternatively, make your selection in the XML tree and\n" | |
+ "a correct XPath expression with corresponding namespace mapping will be generated.\n\n" | |
+ "-------------------------------------------------------------------------------------------------------------\n\n" | |
+ "XPath processing library reported the following error:\n" | |
+ e.getMessage(), "XPath Activity", | |
JOptionPane.ERROR_MESSAGE); | |
} | |
return (-1); | |
} | |
// ----- DISPLAYING THE RESULTS ----- | |
if (displayResults) { | |
tfExecutedXPathExpression.setText(expr.getText()); | |
tfMatchingElementCount.setText("" + matchingNodeCount); | |
StringBuffer outNodesText = new StringBuffer(); | |
StringBuffer outNodesXML = new StringBuffer(); | |
for (Node n : matchingNodes) { | |
if (n.getStringValue() != null | |
&& n.getStringValue().length() > 0) { | |
outNodesText.append(n.getStringValue() + "\n"); | |
} | |
outNodesXML.append(n.asXML() + "\n"); | |
} | |
// tpExecutedXPathExpressionResults.setSelectedIndex(0); // open the | |
// first tab (should be the one with textual results) // TODO - | |
// enable if needed | |
taExecutedXPathExpressionResultsAsText.setText(outNodesText | |
.toString()); | |
taExecutedXPathExpressionResultsAsText.setBackground(Color.WHITE); | |
taExecutedXPathExpressionResultsAsText.setCaretPosition(0); | |
spExecutedXPathExpressionResultsAsText.setBorder(BorderFactory | |
.createLineBorder(Color.WHITE, 3)); | |
taExecutedXPathExpressionResultsAsXML.setText(outNodesXML | |
.toString()); | |
taExecutedXPathExpressionResultsAsXML.setBackground(Color.WHITE); | |
taExecutedXPathExpressionResultsAsXML.setCaretPosition(0); | |
spExecutedXPathExpressionResultsAsXML.setBorder(BorderFactory | |
.createLineBorder(Color.WHITE, 3)); | |
} | |
return (matchingNodeCount); | |
} | |
protected void setSourceXML(String xmlData) { | |
this.taSourceXML.setText(xmlData); | |
} | |
protected String getCurrentXPathExpression() { | |
return (this.tfXPathExpression.getText().trim()); | |
} | |
protected void setCurrentXPathExpression(String xpathExpression) { | |
this.tfXPathExpression.setText(xpathExpression); | |
} | |
protected Map<String, String> getCurrentXPathNamespaceMap() { | |
return (this.xpathNamespaceMap); | |
} | |
/** | |
* This method doesn't simply set a reference to the passed map, but rather | |
* performs a shallow copy of values. | |
* | |
* This is because the method is used during configuration panel's | |
* initialisation from the values that are held in the configuration bean. | |
* In case of simple reference assignment, any changes made to map in the | |
* configuration panel are also taking effect on the same map - referenced | |
* from the configuration bean, which leads to undesired behaviour. | |
*/ | |
protected void setCurrentXPathNamespaceMapValues( | |
Map<String, String> xpathNamespaceMap) { | |
this.xpathNamespaceMap.clear(); | |
this.xpathNamespaceMap.putAll(xpathNamespaceMap); | |
} | |
protected XPathActivityXMLTree getCurrentXMLTree() { | |
return (this.xmlTree); | |
} | |
/** | |
* For testing | |
*/ | |
public static void main(String[] args) { | |
JFrame frame = new JFrame(); | |
frame.getContentPane().add(new XPathActivityConfigurationPanel()); | |
frame.pack(); | |
frame.setSize(new Dimension(900, 600)); | |
frame.setLocationRelativeTo(null); | |
frame.setVisible(true); | |
} | |
} |