You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by st...@apache.org on 2015/02/23 11:24:49 UTC
[02/16] incubator-taverna-workbench-common-activities git commit:
Revert "Temporarily empty repository"
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench-common-activities/blob/54050685/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/XPathActivityConfigurationPanel.java
----------------------------------------------------------------------
diff --git a/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/XPathActivityConfigurationPanel.java b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/XPathActivityConfigurationPanel.java
new file mode 100644
index 0000000..4342a4a
--- /dev/null
+++ b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/XPathActivityConfigurationPanel.java
@@ -0,0 +1,1202 @@
+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);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench-common-activities/blob/54050685/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/XPathActivityConfigurationPanelProvider.java
----------------------------------------------------------------------
diff --git a/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/XPathActivityConfigurationPanelProvider.java b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/XPathActivityConfigurationPanelProvider.java
new file mode 100644
index 0000000..5954986
--- /dev/null
+++ b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/XPathActivityConfigurationPanelProvider.java
@@ -0,0 +1,158 @@
+package net.sf.taverna.t2.activities.xpath.ui.config;
+
+import java.awt.BorderLayout;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.swing.BorderFactory;
+import javax.swing.JOptionPane;
+
+import net.sf.taverna.t2.activities.xpath.XPathActivityConfigurationBean;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ActivityConfigurationPanel;
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+/**
+ *
+ * @author Sergejs Aleksejevs
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class XPathActivityConfigurationPanelProvider extends ActivityConfigurationPanel {
+
+ private XPathActivityConfigurationPanel configPanel;
+ private final ServiceRegistry serviceRegistry;
+
+ public XPathActivityConfigurationPanelProvider(Activity activity, ServiceRegistry serviceRegistry) {
+ super(activity);
+ this.serviceRegistry = serviceRegistry;
+ initialise();
+ }
+
+ @Override
+ protected void initialise() {
+ super.initialise();
+ removeAll();
+ setLayout(new BorderLayout());
+
+ // create actual contents of the config panel
+ this.configPanel = new XPathActivityConfigurationPanel();
+ add(configPanel, BorderLayout.CENTER);
+
+ // place the whole configuration panel into a raised area, so that
+ // automatically added 'Apply' / 'Close' buttons visually apply to
+ // the whole of the panel, not just part of it
+ this.setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createEmptyBorder(12, 12, 2, 12),
+ BorderFactory.createRaisedBevelBorder()));
+
+ // Populate fields from activity configuration bean
+ refreshConfiguration();
+ }
+
+ /**
+ * Prepare a new configuration bean from the UI, to be returned with
+ * getConfiguration()
+ */
+ @Override
+ public void noteConfiguration() {
+ if (configPanel.getCurrentXMLTree() != null) {
+ setProperty("exampleXmlDocument", configPanel.getCurrentXMLTree()
+ .getDocumentUsedToPopulateTree().asXML());
+ }
+ setProperty("xpathExpression", configPanel.getCurrentXPathExpression());
+
+ Map<String, String> xPathNamespaceMap = configPanel.getCurrentXPathNamespaceMap();
+ if (xPathNamespaceMap.isEmpty()) {
+ json.remove("xpathNamespaceMap");
+ } else {
+ ArrayNode namespaceMapNode = json.arrayNode();
+ for (Entry<String, String> namespaceMapping : xPathNamespaceMap.entrySet()) {
+ namespaceMapNode.addObject().put("prefix", namespaceMapping.getKey()).put("uri", namespaceMapping.getValue());
+ }
+ json.set("xpathNamespaceMap", namespaceMapNode);
+ }
+
+ configureInputPorts(serviceRegistry);
+ configureOutputPorts(serviceRegistry);
+}
+
+ /**
+ * Check that user values in the UI are valid.
+ */
+ @Override
+ public boolean checkValues() {
+ // the only validity condition is the correctness of the XPath
+ // expression -- so checking that
+ int xpathExpressionStatus = XPathActivityConfigurationBean.validateXPath(this.configPanel
+ .getCurrentXPathExpression());
+
+ // show an explicit warning message to explain the problem
+ if (xpathExpressionStatus == XPathActivityConfigurationBean.XPATH_EMPTY) {
+ JOptionPane.showMessageDialog(this, "XPath expression should not be empty",
+ "XPath Activity", JOptionPane.WARNING_MESSAGE);
+ } else if (xpathExpressionStatus == XPathActivityConfigurationBean.XPATH_INVALID) {
+ JOptionPane.showMessageDialog(this,
+ "<html><center>XPath expression is invalid - hover the mouse over the XPath status<br>"
+ + "icon to get more information</center></html>", "XPath Activity",
+ JOptionPane.WARNING_MESSAGE);
+ }
+
+ return (xpathExpressionStatus == XPathActivityConfigurationBean.XPATH_VALID);
+ }
+
+ /**
+ * Update GUI from a changed configuration bean (perhaps by undo / redo).
+ */
+ @Override
+ public void refreshConfiguration() {
+ if (json.has("exampleXmlDocument")) {
+ configPanel.setSourceXML(getProperty("exampleXmlDocument"));
+ configPanel.parseXML();
+ }
+
+ configPanel.setCurrentXPathExpression(getProperty("xpathExpression"));
+
+ Map<String, String> xpathNamespaceMap = new HashMap<>();
+ if (json.has("xpathNamespaceMap")) {
+ for (JsonNode namespaceMapping : json.get("xpathNamespaceMap")) {
+ xpathNamespaceMap.put(namespaceMapping.get("prefix").asText(), namespaceMapping.get("uri").asText());
+ }
+ }
+ configPanel.setCurrentXPathNamespaceMapValues(xpathNamespaceMap);
+ configPanel.reloadNamespaceMappingTableFromLocalMap();
+
+ // if the XML tree was populated, (re-)run the XPath expression
+ // and restore selection of nodes in the tree, if possible
+ if (configPanel.getCurrentXMLTree() != null) {
+ configPanel.runXPath(true);
+
+ // convert the XPath expression into the required list form;
+ // discard the first 'leg', as it's a side effect of
+ // "String.split()" -
+ // non-existent string to the left of the first "/"
+ String[] xpathLegs = getProperty("xpathExpression").split("/");
+ List<String> xpathLegList = new ArrayList<String>();
+ for (int i = 1; i < xpathLegs.length; i++) {
+ xpathLegList.add("/" + xpathLegs[i]);
+ }
+
+ // if nothing was obtained, we should be looking at the root node -
+ // but add the actual expression as it was, just in case
+ if (xpathLegList.size() == 0) {
+ xpathLegList.add(configPanel.getCurrentXPathExpression());
+ }
+
+ // invoke selection handler of the XML tree to do the job
+ configPanel.getCurrentXMLTree().getXMLTreeSelectionHandler()
+ .selectAllNodesThatMatchTheCurrentXPath(xpathLegList, null);
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench-common-activities/blob/54050685/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/XPathActivityConfigureAction.java
----------------------------------------------------------------------
diff --git a/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/XPathActivityConfigureAction.java b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/XPathActivityConfigureAction.java
new file mode 100644
index 0000000..4e68948
--- /dev/null
+++ b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/XPathActivityConfigureAction.java
@@ -0,0 +1,52 @@
+package net.sf.taverna.t2.activities.xpath.ui.config;
+
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+
+import net.sf.taverna.t2.servicedescriptions.ServiceDescriptionRegistry;
+import net.sf.taverna.t2.workbench.activityicons.ActivityIconManager;
+import net.sf.taverna.t2.workbench.edits.EditManager;
+import net.sf.taverna.t2.workbench.file.FileManager;
+import net.sf.taverna.t2.workbench.ui.actions.activity.ActivityConfigurationAction;
+import net.sf.taverna.t2.workbench.ui.views.contextualviews.activity.ActivityConfigurationDialog;
+import uk.org.taverna.commons.services.ServiceRegistry;
+import uk.org.taverna.scufl2.api.activity.Activity;
+
+/**
+ * @author Sergejs Aleksejevs
+ * @author David Withers
+ */
+@SuppressWarnings("serial")
+public class XPathActivityConfigureAction extends ActivityConfigurationAction {
+
+ private final EditManager editManager;
+ private final FileManager fileManager;
+ private final ServiceRegistry serviceRegistry;
+
+ public XPathActivityConfigureAction(Activity activity, Frame owner,
+ EditManager editManager, FileManager fileManager,
+ ActivityIconManager activityIconManager,
+ ServiceDescriptionRegistry serviceDescriptionRegistry, ServiceRegistry serviceRegistry) {
+ super(activity, activityIconManager, serviceDescriptionRegistry);
+ this.editManager = editManager;
+ this.fileManager = fileManager;
+ this.serviceRegistry = serviceRegistry;
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ ActivityConfigurationDialog currentDialog = ActivityConfigurationAction.getDialog(getActivity());
+
+ if (currentDialog != null) {
+ currentDialog.toFront();
+ return;
+ }
+
+ XPathActivityConfigurationPanelProvider panel = new XPathActivityConfigurationPanelProvider(
+ getActivity(), serviceRegistry);
+ ActivityConfigurationDialog dialog = new ActivityConfigurationDialog(
+ getActivity(), panel, editManager);
+
+ ActivityConfigurationAction.setDialog(getActivity(), dialog, fileManager);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench-common-activities/blob/54050685/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/TableCellListener.java
----------------------------------------------------------------------
diff --git a/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/TableCellListener.java b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/TableCellListener.java
new file mode 100644
index 0000000..872fd70
--- /dev/null
+++ b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/TableCellListener.java
@@ -0,0 +1,186 @@
+package net.sf.taverna.t2.activities.xpath.ui.config.xmltree;
+
+import java.awt.event.*;
+import javax.swing.*;
+import java.beans.*;
+
+/**
+ * This class listens for changes made to the data in the table via the
+ * TableCellEditor. When editing is started, the value of the cell is saved
+ * When editing is stopped the new value is saved. When the oold and new
+ * values are different, then the provided Action is invoked.
+ *
+ * The source of the Action is a TableCellListener instance.
+ *
+ * TODO: update to work with Java 1.6, when Taverna is migrated to that version
+ * (see the next TODO tag - this is where the change needs to be made)
+ *
+ * @author Robert Camick
+ *
+ * @see <a href="http://tips4java.wordpress.com/2009/06/07/table-cell-listener/">http://tips4java.wordpress.com/2009/06/07/table-cell-listener/</a>
+ * @see <a href="http://www.camick.com/java/source/TableCellListener.java">http://www.camick.com/java/source/TableCellListener.java</a>
+ */
+public class TableCellListener implements PropertyChangeListener, Runnable
+{
+ private JTable table;
+ private Action action;
+
+ private int row;
+ private int column;
+ private Object oldValue;
+ private Object newValue;
+
+ /**
+ * Create a TableCellListener.
+ *
+ * @param table the table to be monitored for data changes
+ * @param action the Action to invoke when cell data is changed
+ */
+ public TableCellListener(JTable table, Action action)
+ {
+ this.table = table;
+ this.action = action;
+ this.table.addPropertyChangeListener( this );
+ }
+
+ /**
+ * Create a TableCellListener with a copy of all the data relevant to
+ * the change of data for a given cell.
+ *
+ * @param row the row of the changed cell
+ * @param column the column of the changed cell
+ * @param oldValue the old data of the changed cell
+ * @param newValue the new data of the changed cell
+ */
+ private TableCellListener(JTable table, int row, int column, Object oldValue, Object newValue)
+ {
+ this.table = table;
+ this.row = row;
+ this.column = column;
+ this.oldValue = oldValue;
+ this.newValue = newValue;
+ }
+
+ /**
+ * Get the column that was last edited - value as in table model, not in the UI
+ *
+ * @return the column that was edited
+ */
+ public int getColumn()
+ {
+ return column;
+ }
+
+ /**
+ * Get the new value in the cell
+ *
+ * @return the new value in the cell
+ */
+ public Object getNewValue()
+ {
+ return newValue;
+ }
+
+ /**
+ * Get the old value of the cell
+ *
+ * @return the old value of the cell
+ */
+ public Object getOldValue()
+ {
+ return oldValue;
+ }
+
+ /**
+ * Get the row that was last edited - value as in table model, not in the UI
+ *
+ * @return the row that was edited
+ */
+ public int getRow()
+ {
+ return row;
+ }
+
+ /**
+ * Get the table of the cell that was changed
+ *
+ * @return the table of the cell that was changed
+ */
+ public JTable getTable()
+ {
+ return table;
+ }
+
+ //
+ // Implement the PropertyChangeListener interface
+ //
+ public void propertyChange(PropertyChangeEvent e)
+ {
+ // A cell has started/stopped editing
+
+ if ("tableCellEditor".equals(e.getPropertyName()))
+ {
+ if (table.isEditing())
+ processEditingStarted();
+ else
+ processEditingStopped();
+ }
+ }
+
+ /*
+ * Save information of the cell about to be edited
+ */
+ private void processEditingStarted()
+ {
+ // The invokeLater is necessary because the editing row and editing
+ // column of the table have not been set when the "tableCellEditor"
+ // PropertyChangeEvent is fired.
+ // This results in the "run" method being invoked
+
+ SwingUtilities.invokeLater( this );
+ }
+
+ /*
+ * See above.
+ */
+ public void run()
+ {
+// TODO - the next line is a correct implementation for Java 1.6
+// row = table.convertRowIndexToModel( table.getEditingRow() );
+
+// in Java 1.5 which is currently used, tables are not easily sortable
+// (and that is the case for the XPath Activity, where this class is used) --
+// hence, row numbers in the view and in the model will always be identical
+// --> HACK: just use the row number from the view...
+ row = table.getEditingRow();
+
+ column = table.convertColumnIndexToModel( table.getEditingColumn() );
+ oldValue = table.getModel().getValueAt(row, column);
+ newValue = null;
+ }
+
+ /*
+ * Update the Cell history when necessary
+ */
+ private void processEditingStopped()
+ {
+ newValue = table.getModel().getValueAt(row, column);
+
+ // The data has changed, invoke the supplied Action
+
+ if (! newValue.equals(oldValue))
+ {
+ // Make a copy of the data in case another cell starts editing
+ // while processing this change
+
+ TableCellListener tcl = new TableCellListener(
+ getTable(), getRow(), getColumn(), getOldValue(), getNewValue());
+
+ ActionEvent event = new ActionEvent(
+ tcl,
+ ActionEvent.ACTION_PERFORMED,
+ "");
+ action.actionPerformed(event);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench-common-activities/blob/54050685/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/XPathActivityXMLTree.java
----------------------------------------------------------------------
diff --git a/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/XPathActivityXMLTree.java b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/XPathActivityXMLTree.java
new file mode 100644
index 0000000..4f98851
--- /dev/null
+++ b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/XPathActivityXMLTree.java
@@ -0,0 +1,572 @@
+package net.sf.taverna.t2.activities.xpath.ui.config.xmltree;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JTree;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.text.Position;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+
+import net.sf.taverna.t2.activities.xpath.ui.config.XPathActivityConfigurationPanel;
+import net.sf.taverna.t2.activities.xpath.ui.servicedescription.XPathActivityIcon;
+
+import org.dom4j.Attribute;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.Namespace;
+import org.dom4j.QName;
+import org.dom4j.XPath;
+
+
+/**
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class XPathActivityXMLTree extends JTree
+{
+ private XPathActivityXMLTree instanceOfSelf;
+ private XPathActivityXMLTreeRenderer treeRenderer;
+
+ private JPopupMenu contextualMenu;
+
+ private TreeSelectionListener[] allSelectionListeners;
+ private XPathActivityXMLTreeSelectionHandler xmlTreeSelectionHandler;
+
+ /**
+ *
+ */
+ private XPathActivityConfigurationPanel parentConfigPanel;
+
+ private Document documentUsedToPopulateTree;
+
+ /**
+ * holds value of the current XPath expression obtained from
+ * the combination of nodes selected in the XML tree
+ */
+ private XPath currentXPathExpression;
+
+ private Map<String,String> currentXPathNamespaces;
+
+
+
+ private XPathActivityXMLTree(XPathActivityXMLTreeNode root, Document documentUsedToPopulateTree,
+ boolean bIncludeElementValues, boolean bIncludeElementNamespaces, XPathActivityConfigurationPanel parentConfigPanel)
+ {
+ super(root);
+
+ this.instanceOfSelf = this;
+ this.allSelectionListeners = new TreeSelectionListener[0];
+
+ this.parentConfigPanel = parentConfigPanel;
+
+ this.documentUsedToPopulateTree = documentUsedToPopulateTree;
+ this.currentXPathExpression = null;
+ this.currentXPathNamespaces = new HashMap<String,String>();
+ this.prepopulateNamespaceMap();
+
+
+ // custom renderer of the nodes in the XML tree
+ this.treeRenderer = new XPathActivityXMLTreeRenderer(bIncludeElementValues, bIncludeElementNamespaces);
+ this.setCellRenderer(treeRenderer);
+
+
+ // add listener to handle various selections of nodes in the tree
+ this.xmlTreeSelectionHandler = new XPathActivityXMLTreeSelectionHandler(parentConfigPanel, this);
+ this.addTreeSelectionListener(xmlTreeSelectionHandler);
+
+
+ // --- CONTEXTUAL MENU FOR EXPANDING / COLLAPSING THE TREE ---
+
+ // create popup menu for expanding / collapsing all nodes in the tree
+ JMenuItem miExpandAll = new JMenuItem("Expand all", XPathActivityIcon.getIconById(XPathActivityIcon.XML_TREE_EXPAND_ALL_ICON));
+ miExpandAll.setToolTipText("Expand all nodes in the filtering tree");
+ miExpandAll.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ for (int i = 0; i < getRowCount(); i++) {
+ instanceOfSelf.expandRow(i);
+ }
+ }
+ });
+ JMenuItem miCollapseAll = new JMenuItem("Collapse all", XPathActivityIcon.getIconById(XPathActivityIcon.XML_TREE_COLLAPSE_ALL_ICON));
+ miCollapseAll.setToolTipText("Collapse all expanded nodes in the filtering tree");
+ miCollapseAll.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ for (int i = getRowCount() - 1; i >= 0; i--) {
+ instanceOfSelf.collapseRow(i);
+ }
+ }
+ });
+
+ // populate the popup menu with created menu items
+ contextualMenu = new JPopupMenu();
+ contextualMenu.add(miExpandAll);
+ contextualMenu.add(miCollapseAll);
+
+ // mouse events may cause the contextual menu to be shown - adding a listener
+ this.addMouseListener(new MouseAdapter()
+ {
+ public void mousePressed(MouseEvent e) {
+ if (e.isPopupTrigger()) {
+ contextualMenu.show(instanceOfSelf, e.getX(), e.getY());
+ }
+ }
+ public void mouseReleased(MouseEvent e) {
+ if (e.isPopupTrigger()) {
+ // another way a popup menu may be called on different systems
+ contextualMenu.show(instanceOfSelf, e.getX(), e.getY());
+ }
+ }
+ });
+
+ }
+
+
+ /**
+ * Pre-populates namespace map with the namespaced declared in the root
+ * node of the XML document, which was used to populate the tree.
+ */
+ private void prepopulateNamespaceMap()
+ {
+ Document doc = this.getDocumentUsedToPopulateTree();
+ Element root = doc.getRootElement();
+
+ // get opening tag of the root node
+ String rootAsXML = root.asXML().substring(0, root.asXML().indexOf(">"));
+
+ // split the opening tag into tokens (all attributes are separated by a space)
+ String[] rootTokens = rootAsXML.split(" ");
+
+ // for each attribute check if that's a namespace declaration
+ for (String token : rootTokens) {
+ if (token.startsWith("xmlns"))
+ {
+ String[] namespacePrefixAndURI = token.split("=");
+
+ // a prefix is either given explicitly, or an empty one will be used
+ String prefix = namespacePrefixAndURI[0].indexOf(":") == -1 ?
+ "" :
+ namespacePrefixAndURI[0].split(":")[1];
+
+ // URI is the value of the XML attribute, so need to strip out surrounding quotes
+ String URI = namespacePrefixAndURI[1].replaceAll("\"", "");
+
+ // now add the details of the current namespace to the map
+ this.addNamespaceToXPathMap(new Namespace(prefix, URI));
+ }
+ }
+ }
+
+
+ protected XPathActivityConfigurationPanel getParentConfigPanel() {
+ return parentConfigPanel;
+ }
+
+ public XPathActivityXMLTreeSelectionHandler getXMLTreeSelectionHandler() {
+ return xmlTreeSelectionHandler;
+ }
+
+ public Document getDocumentUsedToPopulateTree() {
+ return documentUsedToPopulateTree;
+ }
+
+ public XPath getCurrentXPathExpression() {
+ return currentXPathExpression;
+ }
+ protected void setCurrentXPathExpression(XPath xpathExpression) {
+ this.currentXPathExpression = xpathExpression;
+ }
+
+
+ public Map<String,String> getCurrentXPathNamespaces() {
+ return currentXPathNamespaces;
+ }
+
+
+
+ protected void removeAllSelectionListeners()
+ {
+ this.allSelectionListeners = this.getTreeSelectionListeners();
+ for (TreeSelectionListener listener : this.allSelectionListeners) {
+ this.removeTreeSelectionListener(listener);
+ }
+ }
+
+ protected void restoreAllSelectionListeners()
+ {
+ for (TreeSelectionListener listener : this.allSelectionListeners) {
+ this.addTreeSelectionListener(listener);
+ }
+ }
+
+
+
+ /**
+ * Creates an instance of the XML tree from provided XML data.
+ *
+ * @param xmlData XML document in the form of a <code>String</code> to
+ * derive the tree from.
+ * @param bIncludeAttributesIntoTree
+ * @param bIncludeValuesIntoTree
+ * @param bIncludeElementNamespacesIntoTree
+ * @param parentConfigPanel
+ * @return
+ * @throws DocumentException if <code>xmlData</code> does not
+ * contain a valid XML document.
+ *
+ */
+ public static XPathActivityXMLTree createFromXMLData(String xmlData, boolean bIncludeAttributesIntoTree,
+ boolean bIncludeValuesIntoTree, boolean bIncludeElementNamespacesIntoTree,
+ XPathActivityConfigurationPanel parentConfigPanel) throws DocumentException
+ {
+ // ----- XML DOCUMENT PARSING -----
+ // try to parse the XML document - the next line will throw an exception if
+ // the document is not well-formed; proceed otherwise
+ Document doc = DocumentHelper.parseText(xmlData);
+ Element rootElement = doc.getRootElement();
+
+
+ // ----- POPULATE XML TREE -----
+ XPathActivityXMLTreeElementNode rootNode = new XPathActivityXMLTreeElementNode(rootElement);
+ populate(rootNode, rootElement, bIncludeAttributesIntoTree);
+
+ return (new XPathActivityXMLTree(rootNode, doc, bIncludeValuesIntoTree, bIncludeElementNamespacesIntoTree, parentConfigPanel));
+ }
+
+
+ /**
+ * Worker method for populating the tree recursively from a list of Elements.
+ *
+ * @param node
+ * @param element
+ */
+ private static void populate(DefaultMutableTreeNode node, Element element,
+ boolean bIncludeAttributesIntoTree)
+ {
+ Iterator<Element> elementIterator = element.elements().iterator();
+ while (elementIterator.hasNext()) {
+ Element childElement = elementIterator.next();
+ XPathActivityXMLTreeElementNode childNode = new XPathActivityXMLTreeElementNode(childElement);
+ node.add(childNode);
+
+ // recursively repeat for all children of the current child element
+ populate(childNode, childElement, bIncludeAttributesIntoTree);
+ }
+
+
+ // add attributes of the element as its children, if necessary
+ if (bIncludeAttributesIntoTree) {
+ List<Attribute> attributes = element.attributes();
+ for (Attribute attribute : attributes) {
+ node.add(new XPathActivityXMLTreeAttributeNode(attribute));
+ }
+ }
+ }
+
+
+ // ---------------- RESPONDING TO REQUESTS TO CHANGE APPEARANCE OF EXISTING TREE -----------------
+
+ /**
+ * NB! May be inefficient, as this solution re-generates the whole tree from
+ * stored XML document and replaces the root node of itself with a newly
+ * generated root node (that will be populated with updated children,
+ * according to the new values of options).
+ *
+ * However, this is a simple solution that will work for now.
+ *
+ * @param bIncludeAttributes
+ * @param bIncludeValues
+ * @param bIncludeNamespaces
+ */
+ public void refreshFromExistingDocument(boolean bIncludeAttributes, boolean bIncludeValues, boolean bIncludeNamespaces)
+ {
+ this.setEnabled(false);
+ removeAllSelectionListeners();
+
+ // store expansion and selection state of the XML tree
+ // see documentation for restoreExpandedPaths() for more details
+ //
+ // stored paths to expanded nodes are quite reliable, as paths are recorded;
+ // stored selected rows are less reliable, as only indices are kept -- however,
+ // the tree is re-created from the same document, so ordering/number of nodes
+ // cannot change (apart from attributes that may be added / removed - the attributes
+ // appear after other child nodes of some node in the tree, therefore only their
+ // selection could be affected)
+ HashMap<String,ArrayList<String>> toExpand = new HashMap<String,ArrayList<String>>();
+ ArrayList<Integer> toSelect = new ArrayList<Integer>();
+ for( int i = 1; i < this.getRowCount(); i++) {
+ if( this.isExpanded(i) ) {
+ TreePath path = this.getPathForRow(i);
+ String parentPath = path.getParentPath().toString();
+ ArrayList<String> values = toExpand.get(parentPath);
+ if(values == null) {
+ values = new ArrayList<String>();
+ }
+ values.add(path.getLastPathComponent().toString());
+ toExpand.put(parentPath, values);
+ }
+ if (this.isRowSelected(i)) {
+ toSelect.add(i);
+ }
+ }
+
+
+ // update presentation options
+ this.treeRenderer.setIncludeElementValues(bIncludeValues);
+ this.treeRenderer.setIncludeElementNamespaces(bIncludeNamespaces);
+
+ // re-create the root node of the tree and replace the old one with it
+ Element rootElement = this.documentUsedToPopulateTree.getRootElement();
+ XPathActivityXMLTreeNode newRootNode = new XPathActivityXMLTreeElementNode(rootElement);
+ populate(newRootNode, rootElement, bIncludeAttributes);
+ ((DefaultTreeModel)this.getModel()).setRoot(newRootNode);
+
+
+ // restore previous state of the tree from saved values
+ restoreExpandedPaths(toExpand, this.getPathForRow(0));
+ restoreSelectedPaths(toSelect);
+
+ this.restoreAllSelectionListeners();
+ this.setEnabled(true);
+ }
+
+
+ /**
+ * This method can only reliably work when the tree is re-generated from the same
+ * XML document, so that number / order of nodes would not change.
+ *
+ * @param toSelect List of indices of rows to re-select after tree was re-generated.
+ */
+ private void restoreSelectedPaths(ArrayList<Integer> toSelect)
+ {
+ if (toSelect == null || toSelect.isEmpty()) return;
+
+ // something definitely needs to be selected, so include root element into selection
+ this.addSelectionRow(0);
+
+ // select all stored rows
+ for (Integer value : toSelect) {
+ this.addSelectionRow(value);
+ }
+ }
+
+
+
+ /**
+ * Taken from: <a href="http://java.itags.org/java-core-gui-apis/58504/">http://java.itags.org/java-core-gui-apis/58504/</a>
+ *
+ * This method recursively expands all previously stored paths.
+ * Works under assumption that the name of the root node did not change.
+ * Otherwise, it can handle changed structure of the tree.
+ *
+ * To achieve its goal, it cannot simply use stored TreePath from your the original tree,
+ * since the paths are invalid after the tree is refreshed. Instead, a HashMap which links
+ * a String representation of the parent tree path to all expanded child node names is used.
+ *
+ * @param toExpand Map which links a String representation of the parent tree path to all
+ * expanded child node names is used.
+ * @param rootPath Path to root node.
+ */
+ void restoreExpandedPaths(HashMap<String,ArrayList<String>> toExpand, TreePath rootPath)
+ {
+ ArrayList<String> values = toExpand.remove(rootPath.toString());
+ if (values == null) return;
+
+ int row = this.getRowForPath(rootPath);
+ for (String value : values)
+ {
+ TreePath nextMatch = this.getNextMatch(value, row, Position.Bias.Forward);
+ this.expandPath(nextMatch);
+ if (toExpand.containsKey(nextMatch.toString())) {
+ restoreExpandedPaths(toExpand, nextMatch);
+ }
+ }
+ }
+
+
+
+ // ---------------- TREE SELECTION MODEL + XPath GENERATION -----------------
+
+
+ protected String generateXPathFromTreePath(TreePath path)
+ {
+ StringBuilder xpath = new StringBuilder();
+
+ for (String leg : generateXPathFromTreePathAsLegList(path)) {
+ xpath.append(leg);
+ }
+
+ return (xpath.toString());
+ }
+
+
+ protected List<String> generateXPathFromTreePathAsLegList(TreePath path)
+ {
+ List<String> pathLegs = new LinkedList<String>();
+
+ TreePath parentPath = path;
+ for (int i = 0; i < path.getPathCount(); i++)
+ {
+ XPathActivityXMLTreeNode lastXMLTreeNodeInThisPath = (XPathActivityXMLTreeNode)parentPath.getLastPathComponent();
+ pathLegs.add(0, this.getXMLTreeNodeEffectiveQualifiedNameAsXPathLeg(lastXMLTreeNodeInThisPath));
+
+ parentPath = parentPath.getParentPath();
+ }
+
+ return (pathLegs);
+ }
+
+
+ protected String getXMLTreeNodeEffectiveQualifiedNameAsXPathLeg(XPathActivityXMLTreeNode node)
+ {
+ QName qname = node.getNodeQName();
+ String effectiveNamespacePrefix = addNamespaceToXPathMap(qname.getNamespace());
+
+ return("/" +
+ (node.isAttribute() ? "@" : "") +
+ (effectiveNamespacePrefix.length() > 0 ? (effectiveNamespacePrefix + ":") : "") +
+ qname.getName());
+ }
+
+
+
+ private String addNamespaceToXPathMap(Namespace namespace)
+ {
+ // EMTPY PREFIX
+ if (namespace.getPrefix().length() == 0) {
+ if (namespace.getURI().length() == 0) {
+ // DEFAULT NAMESPACE with no URI - nothing to worry about
+ return "";
+ }
+ else {
+ // DEFAULT NAMESPACE WITH NO PREFIX, BUT URI IS KNOWN
+ return (addNamespaceToXPathMap(new Namespace("default", namespace.getURI())));
+ }
+ }
+
+ // NEW NON-EMPTY PREFIX
+ if (!this.currentXPathNamespaces.containsKey(namespace.getPrefix())) {
+ this.currentXPathNamespaces.put(namespace.getPrefix(), namespace.getURI());
+ return (namespace.getPrefix());
+ }
+
+ // EXISTING NON-EMPTY PREFIX AND THE SAME URI - NO NEED TO ADD AGAIN
+ else if (this.currentXPathNamespaces.get(namespace.getPrefix()).equals(namespace.getURI())) {
+ return (namespace.getPrefix());
+ }
+
+ // EXISTING NON-EMPTY PREFIX, BUT DIFFERENT URI
+ else {
+ String repeatedPrefix = namespace.getPrefix();
+
+ int i = 0;
+ while (this.currentXPathNamespaces.containsKey(repeatedPrefix + i)) {
+ // check if current alternative prefix wasn't yet applied to current URI
+ if (this.currentXPathNamespaces.get(repeatedPrefix + i).equals(namespace.getURI())) {
+ return (repeatedPrefix + i);
+ }
+ else {
+ // still another URI for the same prefix, keep trying to increase the ID in the prefix
+ i++;
+ }
+ }
+
+ String modifiedPrefix = repeatedPrefix + i;
+ this.currentXPathNamespaces.put(modifiedPrefix, namespace.getURI());
+ return (modifiedPrefix);
+ }
+ }
+
+
+ // ----------------------- Tree Cell Renderer --------------------------
+
+ /**
+ *
+ * @author Sergejs Aleksejevs
+ */
+ private class XPathActivityXMLTreeRenderer extends DefaultTreeCellRenderer
+ {
+ private boolean bIncludeElementValues;
+ private boolean bIncludeElementNamespaces;
+
+ public XPathActivityXMLTreeRenderer(boolean bIncludeElementValues, boolean bIncludeElementNamespaces) {
+ super();
+ this.bIncludeElementValues = bIncludeElementValues;
+ this.bIncludeElementNamespaces = bIncludeElementNamespaces;
+ }
+
+
+ public boolean getIncludeElementValues() {
+ return bIncludeElementValues;
+ }
+ public void setIncludeElementValues(boolean bIncludeElementValues) {
+ this.bIncludeElementValues = bIncludeElementValues;
+ }
+
+ public boolean getIncludeElementNamespaces() {
+ return bIncludeElementNamespaces;
+ }
+ public void setIncludeElementNamespaces(boolean bIncludeElementNamespaces) {
+ this.bIncludeElementNamespaces = bIncludeElementNamespaces;
+ }
+
+
+ public Component getTreeCellRendererComponent(JTree tree, Object value,
+ boolean selected, boolean expanded, boolean leaf, int row,
+ boolean hasFocus)
+ {
+ // obtain the default rendering, we'll then customize it
+ Component defaultRendering = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
+
+ // it is most likely that the default rendering will be a JLabel, check just to be safe
+ if (defaultRendering instanceof JLabel)
+ {
+ JLabel defaultRenderedLabel = ((JLabel)defaultRendering);
+
+ // ---------- CHOOSE APPROPRIATE ICON FOR THE NODE ------------
+ if (row == 0) {
+ // set the icon for the XML tree root node
+ defaultRenderedLabel.setIcon(XPathActivityIcon.getIconById(XPathActivityIcon.XML_TREE_ROOT_ICON));
+ }
+ else {
+ // set the icon for the XML tree node
+ if (value instanceof XPathActivityXMLTreeNode &&
+ ((XPathActivityXMLTreeNode)value).isAttribute())
+ {
+ defaultRenderedLabel.setIcon(XPathActivityIcon.getIconById(XPathActivityIcon.XML_TREE_ATTRIBUTE_ICON));
+ }
+ else {
+ defaultRenderedLabel.setIcon(XPathActivityIcon.getIconById(XPathActivityIcon.XML_TREE_NODE_ICON));
+ }
+ }
+
+
+ // ----------- CHOOSE THE DISPLAY TITLE FOR THE NODE ------------
+ if (value instanceof XPathActivityXMLTreeNode) {
+ defaultRenderedLabel.setText(((XPathActivityXMLTreeNode)value).getTreeNodeDisplayLabel(
+ this.bIncludeElementValues, this.bIncludeElementNamespaces, true));
+ }
+ }
+
+ return (defaultRendering);
+ }
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench-common-activities/blob/54050685/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/XPathActivityXMLTreeAttributeNode.java
----------------------------------------------------------------------
diff --git a/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/XPathActivityXMLTreeAttributeNode.java b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/XPathActivityXMLTreeAttributeNode.java
new file mode 100644
index 0000000..d22623f
--- /dev/null
+++ b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/XPathActivityXMLTreeAttributeNode.java
@@ -0,0 +1,50 @@
+package net.sf.taverna.t2.activities.xpath.ui.config.xmltree;
+
+import org.dom4j.Attribute;
+
+/**
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class XPathActivityXMLTreeAttributeNode extends XPathActivityXMLTreeNode
+{
+ private Attribute associatedAttribute;
+
+ public XPathActivityXMLTreeAttributeNode(Attribute associatedAttribute) {
+ super(associatedAttribute, true);
+ this.associatedAttribute = associatedAttribute;
+ }
+
+ public Attribute getAssociatedAttribute() {
+ return associatedAttribute;
+ }
+
+ public String getTreeNodeDisplayLabel(boolean bIncludeValue, boolean bUseStyling)
+ {
+ StringBuilder label = new StringBuilder();
+
+ // add qualified attribute name (possibly) with styling
+ label.append((bUseStyling ? "<font color=\"purple\">" : "") +
+ this.associatedAttribute.getQualifiedName() +
+ (bUseStyling ? "</font>" : ""));
+
+ // add attribute value
+ if (bIncludeValue)
+ {
+ String attributeTextValue = this.associatedAttribute.getText();
+
+ if (attributeTextValue != null && attributeTextValue.length() > 0) {
+ label.append((bUseStyling ? "<font color=\"gray\"> - </font><font color=\"green\">" : "") +
+ truncateElementTextValue(stripAllHTML(attributeTextValue)) +
+ (bUseStyling ? "</font>" : ""));
+ }
+ }
+
+ if (bUseStyling) {
+ label.insert(0, "<html>");
+ label.append("</html>");
+ }
+
+ return (label.toString());
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench-common-activities/blob/54050685/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/XPathActivityXMLTreeElementNode.java
----------------------------------------------------------------------
diff --git a/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/XPathActivityXMLTreeElementNode.java b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/XPathActivityXMLTreeElementNode.java
new file mode 100644
index 0000000..a151753
--- /dev/null
+++ b/taverna-xpath-activity-ui/src/main/java/net/sf/taverna/t2/activities/xpath/ui/config/xmltree/XPathActivityXMLTreeElementNode.java
@@ -0,0 +1,62 @@
+package net.sf.taverna.t2.activities.xpath.ui.config.xmltree;
+
+import org.dom4j.Element;
+import org.dom4j.Namespace;
+
+
+/**
+ *
+ * @author Sergejs Aleksejevs
+ */
+public class XPathActivityXMLTreeElementNode extends XPathActivityXMLTreeNode
+{
+ private Element associatedElement;
+
+ public XPathActivityXMLTreeElementNode(Element associatedElement) {
+ super(associatedElement, false);
+ this.associatedElement = associatedElement;
+ }
+
+ public Element getAssociatedElement() {
+ return associatedElement;
+ }
+
+ public String getTreeNodeDisplayLabel(boolean bIncludeValue, boolean bIncludeNamespace, boolean bUseStyling)
+ {
+ StringBuilder label = new StringBuilder();
+
+ // add qualified element name
+ label.append(this.associatedElement.getQualifiedName());
+
+ // add element namespace
+ if (bIncludeNamespace)
+ {
+ Namespace ns = this.associatedElement.getNamespace();
+
+ label.append((bUseStyling ? "<font color=\"gray\">" : "") +
+ " - xmlns" + (ns.getPrefix().length() > 0 ? (":" + ns.getPrefix()) : "") + "=\"" +
+ this.associatedElement.getNamespaceURI() +
+ (bUseStyling ? "\"</font>" : ""));
+ }
+
+ // add element value
+ if (bIncludeValue)
+ {
+ String elementTextValue = this.associatedElement.getTextTrim();
+
+ if (elementTextValue != null && elementTextValue.length() > 0) {
+ label.append((bUseStyling ? "<font color=\"gray\"> - </font><font color=\"blue\">" : "") +
+ truncateElementTextValue(stripAllHTML(elementTextValue)) +
+ (bUseStyling ? "</font>" : ""));
+ }
+ }
+
+ if (bUseStyling) {
+ label.insert(0, "<html>");
+ label.append("</html>");
+ }
+
+ return (label.toString());
+ }
+
+}
\ No newline at end of file