blob: 389bf1a9f29eb14291bf676e323a1fc7c99ad3b2 [file] [log] [blame]
package net.sf.taverna.t2.activities.xpath.ui.config.xmltree;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.swing.JOptionPane;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import net.sf.taverna.t2.activities.xpath.ui.config.XPathActivityConfigurationPanel;
import org.dom4j.DocumentHelper;
/**
*
* @author Sergejs Aleksejevs
*/
public class XPathActivityXMLTreeSelectionHandler implements TreeSelectionListener
{
private final XPathActivityXMLTree theTree;
private final XPathActivityConfigurationPanel parentConfigPanel;
public XPathActivityXMLTreeSelectionHandler(XPathActivityConfigurationPanel parentConfigPanel,
XPathActivityXMLTree tree)
{
this.parentConfigPanel = parentConfigPanel;
this.theTree = tree;
}
public void valueChanged(TreeSelectionEvent e)
{
// get the newly made selection
TreePath newSelectedPath = e.getNewLeadSelectionPath();
// NB! Safety check - sometimes the container of the XML tree will remove all selections,
// in such case this listener is not supposed to perform any action -> terminate
if (newSelectedPath == null) return;
// --- XPath GENERATION ---
// get the XPath expression for the new selection + taking into consideration all previous ones
List<String> wildcardedXPath = generateWildcardedXPathExpression(newSelectedPath);
// assemble the xpath expression as one string
StringBuilder xpath = new StringBuilder();
for (String leg : wildcardedXPath) {
xpath.append(leg);
}
theTree.setCurrentXPathExpression(DocumentHelper.createXPath(xpath.toString()));
theTree.getCurrentXPathExpression().setNamespaceURIs(theTree.getCurrentXPathNamespaces());
// --- UPDATE CONFIG PANEL ---
// (with new values for XPath expression and namespace mappings)
// inform the parent activity configuration panel to update the XPath expression in the UI
/* We do not update the XPath expression after changes in selection in the XML tree - we
* now have a button to explicitly do that.
* theTree.getParentConfigPanel().updateXPathEditingPanelValues();
*/
// --- SELECTION ---
selectAllNodesThatMatchTheCurrentXPath(wildcardedXPath, newSelectedPath);
}
/**
* Selects all nodes that match the <code>wildcardedXPath</code> expression.
*
* Keyboard focus is set to remain on the "deepest" (e.g. furthest from root)
* element of the <code>lastSelectedPath</code>.
*
* @param wildcardedXPath List of strings, where each string contains one "leg" of the XPath expression
* (e.g. a string starting with a "/" and containing the name of one node of the tree).
*
* @param lastSelectedPath The path that was last selected in the tree (normally,
* because of this selection {@link XPathActivityXMLTreeSelectionHandler#valueChanged(TreeSelectionEvent)}
* was executed and this method was started as a part of that.
*/
public void selectAllNodesThatMatchTheCurrentXPath(List<String> wildcardedXPath, TreePath lastSelectedPath)
{
// first of all - calculate the number of nodes that match this XPath
// expression in the XML tree
int numberOfMatchingNodes = parentConfigPanel.runXPath(false);
// store all tree selection listeners in order to temporarily remove them;
// this is necessary as selection modifications will be made here -- don't
// want any listeners to respond to these new events
theTree.removeAllSelectionListeners();
// remove all previous selections - safest way to get the new ones correctly
theTree.clearSelection();
if (numberOfMatchingNodes <= XPathActivityConfigurationPanel.MAX_NUMBER_OF_MATCHING_NODES_TO_HIGHLIGHT_IN_THE_TREE)
{
// find all nodes that match the XPath expression
List<XPathActivityXMLTreeNode> matchingNodes = new ArrayList<XPathActivityXMLTreeNode>();
findAllNodesThatMatchWildcardedXPath(
(XPathActivityXMLTreeNode)theTree.getModel().getRoot(),
wildcardedXPath.subList(1, wildcardedXPath.size()),
matchingNodes);
// obtain and select TreePaths for each of the matching nodes
for (XPathActivityXMLTreeNode matchingNode : matchingNodes) {
TreeNode[] pathAsObjects = ((DefaultTreeModel)theTree.getModel()).getPathToRoot(matchingNode);
TreePath path = new TreePath(pathAsObjects);
selectTreePathAndAllItsAncestors(path);
}
}
else {
JOptionPane.showMessageDialog(parentConfigPanel,
"Current XPath expression matches " + numberOfMatchingNodes + " nodes in the XML tree.\n" +
"The XPath Activity is unable to highlight all these nodes in the tree due to\n" +
"performance reasons.\n\n" +
"The XPath Activity will still work correctly - both during the workflow execution\n" +
"and if 'Run XPath' button is clicked to run this expression against the example XML.",
"XPath Activity", JOptionPane.INFORMATION_MESSAGE);
}
// make sure the keyboard focus stays on the actual node that was clicked on -
// no direct way to do this, so simply de-select and re-select again
if (lastSelectedPath != null) {
theTree.removeSelectionPath(lastSelectedPath);
theTree.addSelectionPath(lastSelectedPath);
}
// restore all previously stored selection listeners
theTree.restoreAllSelectionListeners();
}
/**
* This cannot work for XPath expressions that were modified manually -
* only works for the type generated by the click in the XML tree.
*
* @param nodeToStartAt
* @param xpathLegs From <code>nodeToStartAt</code>.
* @param matchingNodes
*/
private void findAllNodesThatMatchWildcardedXPath(XPathActivityXMLTreeNode nodeToStartAt,
List<String> xpathLegs, List<XPathActivityXMLTreeNode> matchingNodes)
{
// some of the input data is missing, just return...
if (nodeToStartAt == null || xpathLegs == null || matchingNodes == null) {
return;
}
// no XPath expression to match against the 'nodeToStartAt', therefore
// we've "found" the macthing node: 'nodeToStartAt'
if (xpathLegs.size() == 0) {
matchingNodes.add(nodeToStartAt);
return;
}
// standard case - there is something to match, proceed as normal
Enumeration<XPathActivityXMLTreeNode> startNodeChildren = nodeToStartAt.children();
while (startNodeChildren.hasMoreElements()) {
XPathActivityXMLTreeNode child = startNodeChildren.nextElement();
if (xpathLegs.get(0).equals("/*") ||
xpathLegs.get(0).equals(this.theTree.getXMLTreeNodeEffectiveQualifiedNameAsXPathLeg(child)))
{
// this node matches current section of the XPath expression
if (xpathLegs.size() == 1) {
// no more sections in the XPath leg list to match, so this child
// node is the one we were looking for - add to the result
matchingNodes.add(child);
}
else {
// ...or process its children recursively
findAllNodesThatMatchWildcardedXPath(child, xpathLegs.subList(1, xpathLegs.size()), matchingNodes);
}
}
}
}
private List<String> generateWildcardedXPathExpression(TreePath newSelectedPath)
{
// look through previous selection to find paths of the same length, as the newly selected one
List<TreePath> pathsOfSameLength = new ArrayList<TreePath>();
TreePath[] previouslySelectedPaths = theTree.getSelectionPaths();
for (TreePath path : previouslySelectedPaths) {
if (path.getPathCount() == newSelectedPath.getPathCount()) {
pathsOfSameLength.add(path);
}
}
// if there were found any paths of the same length, we have a "wildcard" situation
List<String> wildcardXPathLegs = theTree.generateXPathFromTreePathAsLegList(newSelectedPath);
if (pathsOfSameLength.size() > 0)
{
// it's okay to use just the first path - TODO: explain that this is because of previous comparisons
List<String> firstMatchingLengthPathLegs = theTree.generateXPathFromTreePathAsLegList(pathsOfSameLength.get(0));
int pathLength = wildcardXPathLegs.size();
// only use wildcards if the last segments of both paths are identical
if (wildcardXPathLegs.get(pathLength - 1).equals(firstMatchingLengthPathLegs.get(pathLength - 1)))
{
// continue all the way to the last segment, but don't touch it
for (int i = 0; i < wildcardXPathLegs.size() - 1; i++)
{
if (!wildcardXPathLegs.get(i).equals(firstMatchingLengthPathLegs.get(i))) {
// set wildcard
// TODO - make wildcard a constant
// TODO - may need to make the wildcard to have a namespace? (e.g. "/default:*" instead of simply "/*")
wildcardXPathLegs.set(i, "/*"); // definitely an element, not an attribute (as not the last segment in the path)
}
}
}
}
return (wildcardXPathLegs);
}
private void selectTreePathAndAllItsAncestors(TreePath path)
{
// select all ancestors of that path
TreePath pathToSelect = path;
for (int i = 0; i < path.getPathCount(); i++)
{
pathToSelect = pathToSelect.getParentPath();
theTree.addSelectionPath(pathToSelect);
}
// select the specified path itself
//
// NB! important to do this after the ancestors, so that the supplied
// path is the one that retains the keyboard focus after this method terminates
theTree.addSelectionPath(path);
}
}