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