Coverage Report - org.jaxen.expr.DefaultNameStep

Classes in this Package Line Coverage Branch Coverage Complexity
DefaultNameStep
92% 
95% 
6.182

 1  
 /*
 2  
  $Id: DefaultNameStep.java,v 1.43 2005/04/17 13:18:44 elharo Exp $
 3  
 
 4  
  Copyright 2003 (C) The Werken Company. All Rights Reserved.
 5  
  
 6  
  Redistribution and use of this software and associated documentation
 7  
  ("Software"), with or without modification, are permitted provided
 8  
  that the following conditions are met:
 9  
 
 10  
  1. Redistributions of source code must retain copyright
 11  
     statements and notices.  Redistributions must also contain a
 12  
     copy of this document.
 13  
  
 14  
  2. Redistributions in binary form must reproduce the
 15  
     above copyright notice, this list of conditions and the
 16  
     following disclaimer in the documentation and/or other
 17  
     materials provided with the distribution.
 18  
  
 19  
  3. The name "jaxen" must not be used to endorse or promote
 20  
     products derived from this Software without prior written
 21  
     permission of The Werken Company.  For written permission,
 22  
     please contact bob@werken.com.
 23  
  
 24  
  4. Products derived from this Software may not be called "jaxen"
 25  
     nor may "jaxen" appear in their names without prior written
 26  
     permission of The Werken Company. "jaxen" is a registered
 27  
     trademark of The Werken Company.
 28  
  
 29  
  5. Due credit should be given to The Werken Company.
 30  
     (http://jaxen.werken.com/).
 31  
  
 32  
  THIS SOFTWARE IS PROVIDED BY THE WERKEN COMPANY AND CONTRIBUTORS
 33  
  ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
 34  
  NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 35  
  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 36  
  THE WERKEN COMPANY OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 37  
  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 38  
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 39  
  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 40  
  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 41  
  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 42  
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 43  
  OF THE POSSIBILITY OF SUCH DAMAGE.
 44  
 
 45  
  */
 46  
 package org.jaxen.expr;
 47  
 
 48  
 import java.util.ArrayList;
 49  
 import java.util.Collections;
 50  
 import java.util.Iterator;
 51  
 import java.util.List;
 52  
 
 53  
 import org.jaxen.Context;
 54  
 import org.jaxen.ContextSupport;
 55  
 import org.jaxen.JaxenException;
 56  
 import org.jaxen.UnresolvableException;
 57  
 import org.jaxen.Navigator;
 58  
 import org.jaxen.expr.iter.IterableAxis;
 59  
 import org.jaxen.saxpath.Axis;
 60  
 
 61  
 /** 
 62  
  * Expression object that represents any flavor
 63  
  * of name-test steps within an XPath.
 64  
  * <p>
 65  
  * This includes simple steps, such as "foo",
 66  
  * non-default-axis steps, such as "following-sibling::foo"
 67  
  * or "@foo", and namespace-aware steps, such
 68  
  * as "foo:bar".
 69  
  *
 70  
  * @author bob mcwhirter (bob@werken.com)
 71  
  * @author Stephen Colebourne
 72  
  */
 73  
 public class DefaultNameStep extends DefaultStep implements NameStep {
 74  
     
 75  
     /** 
 76  
      * Our prefix, bound through the current Context.
 77  
      * The empty-string ("") if no prefix was specified.
 78  
      * Decidedly NOT-NULL, due to SAXPath constraints.
 79  
      * This is the 'foo' in 'foo:bar'.
 80  
      */
 81  
     private String prefix;
 82  
 
 83  
     /**
 84  
      * Our local-name.
 85  
      * This is the 'bar' in 'foo:bar'.
 86  
      */
 87  
     private String localName;
 88  
 
 89  
     /** Quick flag denoting if the local name was '*' */
 90  
     private boolean matchesAnyName;
 91  
 
 92  
     /** Quick flag denoting if we have a namespace prefix **/
 93  
     private boolean hasPrefix;
 94  
 
 95  
     /**
 96  
      * Constructor.
 97  
      * 
 98  
      * @param axis  the axis to work through
 99  
      * @param prefix  the name prefix
 100  
      * @param localName  the local name
 101  
      * @param predicateSet  the set of predicates
 102  
      */    
 103  
     public DefaultNameStep(IterableAxis axis,
 104  
                            String prefix,
 105  
                            String localName,
 106  
                            PredicateSet predicateSet) {
 107  2161
         super(axis, predicateSet);
 108  
 
 109  2161
         this.prefix = prefix;
 110  2161
         this.localName = localName;
 111  2161
         this.matchesAnyName = "*".equals(localName);
 112  2161
         this.hasPrefix = (this.prefix != null && this.prefix.length() > 0);
 113  2161
     }
 114  
 
 115  
     /**
 116  
      * Gets the namespace prefix.
 117  
      * 
 118  
      * @return the prefix
 119  
      */
 120  
     public String getPrefix() {
 121  50
         return this.prefix;
 122  
     }
 123  
 
 124  
     /**
 125  
      * Gets the local name.
 126  
      * 
 127  
      * @return the local name
 128  
      */
 129  
     public String getLocalName() {
 130  148091
         return this.localName;
 131  
     }
 132  
 
 133  
     /**
 134  
      * Does this step match any name? (i.e. Is it '*'?)
 135  
      * 
 136  
      * @return true if it matches any name
 137  
      */
 138  
     public boolean isMatchesAnyName() {
 139  32
         return matchesAnyName;
 140  
     }
 141  
 
 142  
     /**
 143  
      * Gets the step as a fully defined XPath.
 144  
      * 
 145  
      * @return the full XPath for this step
 146  
      */
 147  
     public String getText() {
 148  9
         StringBuffer buf = new StringBuffer(64);
 149  9
         buf.append(getAxisName()).append("::");
 150  9
         if (getPrefix() != null && getPrefix().length() > 0) {
 151  0
             buf.append(getPrefix()).append(':');
 152  
         }
 153  9
         return buf.append(getLocalName()).append(super.getText()).toString();
 154  
     }
 155  
 
 156  
     /**
 157  
      * Evaluate the context node set to find the new node set.
 158  
      * <p>
 159  
      * This method overrides the version in <code>DefaultStep</code> for performance.
 160  
      */
 161  
     public List evaluate(Context context) throws JaxenException {
 162  
 
 163  4261
         List contextNodeSet  = context.getNodeSet();
 164  4261
         int contextSize = contextNodeSet.size();
 165  
         // optimize for context size 0
 166  4261
         if (contextSize == 0) {
 167  21
             return Collections.EMPTY_LIST;
 168  
         }
 169  4240
         ContextSupport support = context.getContextSupport();
 170  4240
         IterableAxis iterableAxis = getIterableAxis();
 171  4240
         boolean namedAccess = (!matchesAnyName && iterableAxis.supportsNamedAccess(support));
 172  
         
 173  
         // optimize for context size 1 (common case, avoids lots of object creation)
 174  4240
         if (contextSize == 1) {
 175  3995
             Object contextNode = contextNodeSet.get(0);
 176  3995
             if (namedAccess) {
 177  
                 // get the iterator over the nodes and check it
 178  1635
                 String uri = null;
 179  1635
                 if (hasPrefix) {
 180  38
                     uri = support.translateNamespacePrefixToUri(prefix);
 181  38
                     if (uri == null) {
 182  0
                         throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
 183  
                     }
 184  
                 }
 185  1635
                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
 186  
                                 contextNode, support, localName, prefix, uri);
 187  1635
                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
 188  452
                     return Collections.EMPTY_LIST;
 189  
                 }
 190  
 
 191  
                 // convert iterator to list for predicate test
 192  
                 // no need to filter as named access guarantees this
 193  1183
                 List newNodeSet = new ArrayList();
 194  5577
                 while (axisNodeIter.hasNext()) {
 195  4394
                     newNodeSet.add(axisNodeIter.next());
 196  
                 }
 197  
                 
 198  
                 // evaluate the predicates
 199  1183
                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
 200  
                 
 201  
             } 
 202  
             else {
 203  
                 // get the iterator over the nodes and check it
 204  2360
                 Iterator axisNodeIter = iterableAxis.iterator(contextNode, support);
 205  2360
                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
 206  467
                     return Collections.EMPTY_LIST;
 207  
                 }
 208  
 
 209  
                 // run through iterator, filtering using matches()
 210  
                 // adding to list for predicate test
 211  1893
                 List newNodeSet = new ArrayList(contextSize);
 212  443775
                 while (axisNodeIter.hasNext()) {
 213  441882
                     Object eachAxisNode = axisNodeIter.next();
 214  441882
                     if (matches(eachAxisNode, support)) {
 215  10391
                         newNodeSet.add(eachAxisNode);
 216  
                     }
 217  
                 }
 218  
                 
 219  
                 // evaluate the predicates
 220  1893
                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
 221  
             }
 222  
         }
 223  
 
 224  
         // full case
 225  245
         IdentitySet unique = new IdentitySet();
 226  245
         List interimSet = new ArrayList(contextSize);
 227  245
         List newNodeSet = new ArrayList(contextSize);
 228  
         
 229  245
         if (namedAccess) {
 230  84
             String uri = null;
 231  84
             if (hasPrefix) {
 232  0
                 uri = support.translateNamespacePrefixToUri(prefix);
 233  0
                 if (uri == null) {
 234  0
                     throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
 235  
                 }
 236  
             }
 237  4361
             for (int i = 0; i < contextSize; ++i) {
 238  4277
                 Object eachContextNode = contextNodeSet.get(i);
 239  
 
 240  4277
                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
 241  
                                 eachContextNode, support, localName, prefix, uri);
 242  4277
                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
 243  4075
                     continue;
 244  
                 }
 245  
 
 246  
                 // ensure only one of each node in the result
 247  734
                 while (axisNodeIter.hasNext()) {
 248  532
                     Object eachAxisNode = axisNodeIter.next();
 249  532
                     if (! unique.contains(eachAxisNode)) {
 250  532
                         unique.add(eachAxisNode);
 251  532
                         interimSet.add(eachAxisNode);
 252  
                     }
 253  
                 }
 254  
 
 255  
                 // evaluate the predicates
 256  202
                 newNodeSet.addAll(getPredicateSet().evaluatePredicates(interimSet, support));
 257  202
                 interimSet.clear();
 258  
             }
 259  
             
 260  
         } else {
 261  13191
             for (int i = 0; i < contextSize; ++i) {
 262  13032
                 Object eachContextNode = contextNodeSet.get(i);
 263  
 
 264  13032
                 Iterator axisNodeIter = axisIterator(eachContextNode, support);
 265  13032
                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
 266  8540
                     continue;
 267  
                 }
 268  
 
 269  
                 // ensure only unique matching nodes in the result
 270  17848
                 while (axisNodeIter.hasNext()) {
 271  13356
                     Object eachAxisNode = axisNodeIter.next();
 272  
 
 273  13356
                     if (matches(eachAxisNode, support)) {
 274  3535
                         if (! unique.contains(eachAxisNode)) {
 275  3533
                             unique.add(eachAxisNode);
 276  3533
                             interimSet.add(eachAxisNode);
 277  
                         }
 278  
                     }
 279  
                 }
 280  
 
 281  
                 // evaluate the predicates
 282  4492
                 newNodeSet.addAll(getPredicateSet().evaluatePredicates(interimSet, support));
 283  4490
                 interimSet.clear();
 284  
             }
 285  
         }
 286  
         
 287  243
         return newNodeSet;
 288  
     }
 289  
     
 290  
     /**
 291  
      * Checks whether the node matches this step.
 292  
      * 
 293  
      * @param node  the node to check
 294  
      * @param contextSupport  the context support
 295  
      * @return true if matches
 296  
      */
 297  
     public boolean matches(Object node, ContextSupport contextSupport) throws JaxenException {
 298  
         
 299  455238
         Navigator nav  = contextSupport.getNavigator();
 300  455238
         String myUri = null;
 301  455238
         String nodeName = null;
 302  455238
         String nodeUri = null;
 303  
 
 304  455238
         if (nav.isElement(node)) {
 305  153696
             nodeName = nav.getElementName(node);
 306  153696
             nodeUri = nav.getElementNamespaceUri(node);
 307  
         } 
 308  301542
         else if (nav.isText(node)) {
 309  299994
             return false;
 310  
         } 
 311  1548
         else if (nav.isAttribute(node)) {
 312  876
             if (getAxis() != Axis.ATTRIBUTE) {
 313  1
                 return false;
 314  
             }
 315  875
             nodeName = nav.getAttributeName(node);
 316  875
             nodeUri = nav.getAttributeNamespaceUri(node);
 317  
             
 318  
         } 
 319  672
         else if (nav.isDocument(node)) {
 320  54
             return false;
 321  
             
 322  
         } 
 323  618
         else if (nav.isNamespace(node)) {
 324  473
             if (matchesAnyName && getAxis() != Axis.NAMESPACE) {
 325  
                 // Only works for namespace::*
 326  5
                 return false;
 327  
             }
 328  468
             nodeName = nav.getNamespacePrefix(node);
 329  
         } 
 330  
         else {
 331  145
             return false;
 332  
         }
 333  
 
 334  155039
         if (hasPrefix) {
 335  65
             myUri = contextSupport.translateNamespacePrefixToUri(this.prefix);
 336  65
             if (myUri == null) {
 337  0
                     throw new UnresolvableException("Cannot resolve namespace prefix '"+this.prefix+"'");
 338  
             }
 339  
         } 
 340  154974
         else if (matchesAnyName) {
 341  6959
             return true;
 342  
         }
 343  
 
 344  
         // If we map to a non-empty namespace and the node does not
 345  
         // or vice-versa, fail-fast.
 346  148080
         if (hasNamespace(myUri) != hasNamespace(nodeUri)) {
 347  28
             return false;
 348  
         }
 349  
         
 350  
         // To fail-fast, we check the equality of
 351  
         // local-names first.  Shorter strings compare
 352  
         // quicker.
 353  148052
         if (matchesAnyName || nodeName.equals(getLocalName())) {
 354  6969
             return matchesNamespaceURIs(myUri, nodeUri);
 355  
         }
 356  
 
 357  141083
         return false;
 358  
     }
 359  
 
 360  
     /**
 361  
      * Checks whether the URI represents a namespace.
 362  
      * 
 363  
      * @param uri  the URI to check
 364  
      * @return true if non-null and non-empty
 365  
      */
 366  
     private boolean hasNamespace(String uri) {
 367  296160
         return (uri != null && uri.length() > 0);
 368  
     }
 369  
 
 370  
     /**
 371  
      * Compares two namespace URIs, handling null.
 372  
      * 
 373  
      * @param uri1  the first URI
 374  
      * @param uri2  the second URI
 375  
      * @return true if equal, where null==""
 376  
      */
 377  
     protected boolean matchesNamespaceURIs(String uri1, String uri2) {
 378  6969
         if (uri1 == uri2) {
 379  3533
             return true;
 380  
         }
 381  3436
         if (uri1 == null) {
 382  3398
             return (uri2.length() == 0);
 383  
         }
 384  38
         if (uri2 == null) {
 385  0
             return (uri1.length() == 0);
 386  
         }
 387  38
         return uri1.equals(uri2);
 388  
     }
 389  
 
 390  
     /**
 391  
      * Visitor pattern for the step.
 392  
      * 
 393  
      * @param visitor  the visitor object
 394  
      */
 395  
     public void accept(Visitor visitor) {
 396  0
         visitor.visit(this);
 397  0
     }
 398  
     
 399  
     /**
 400  
      * Returns a full information debugging string.
 401  
      * 
 402  
      * @return a debugging string
 403  
      */
 404  
     public String toString() {
 405  0
         return "[(DefaultNameStep): " + getPrefix() + ":" + getLocalName() + "[" + super.toString() + "]]";
 406  
     }
 407  
 
 408  
 }