Coverage Report - org.jaxen.expr.DefaultNameStep

Classes in this File Line Coverage Branch Coverage Complexity
DefaultNameStep
93% 
95% 
6.182

 1  
 /*
 2  
  $Id: DefaultNameStep.java,v 1.47 2005/09/24 23:01:50 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  
  * @deprecated this class will become non-public in the future;
 73  
  *     use the interface instead
 74  
  */
 75  
 public class DefaultNameStep extends DefaultStep implements NameStep {
 76  
     
 77  
     /** 
 78  
      * Our prefix, bound through the current Context.
 79  
      * The empty-string ("") if no prefix was specified.
 80  
      * Decidedly NOT-NULL, due to SAXPath constraints.
 81  
      * This is the 'foo' in 'foo:bar'.
 82  
      */
 83  
     private String prefix;
 84  
 
 85  
     /**
 86  
      * Our local-name.
 87  
      * This is the 'bar' in 'foo:bar'.
 88  
      */
 89  
     private String localName;
 90  
 
 91  
     /** Quick flag denoting if the local name was '*' */
 92  
     private boolean matchesAnyName;
 93  
 
 94  
     /** Quick flag denoting if we have a namespace prefix **/
 95  
     private boolean hasPrefix;
 96  
 
 97  
     /**
 98  
      * Constructor.
 99  
      * 
 100  
      * @param axis  the axis to work through
 101  
      * @param prefix  the name prefix
 102  
      * @param localName  the local name
 103  
      * @param predicateSet  the set of predicates
 104  
      */    
 105  
     public DefaultNameStep(IterableAxis axis,
 106  
                            String prefix,
 107  
                            String localName,
 108  
                            PredicateSet predicateSet) {
 109  13158
         super(axis, predicateSet);
 110  
 
 111  13158
         this.prefix = prefix;
 112  13158
         this.localName = localName;
 113  13158
         this.matchesAnyName = "*".equals(localName);
 114  13158
         this.hasPrefix = (this.prefix != null && this.prefix.length() > 0);
 115  13158
     }
 116  
 
 117  
     /**
 118  
      * Gets the namespace prefix.
 119  
      * 
 120  
      * @return the prefix
 121  
      */
 122  
     public String getPrefix() {
 123  276
         return this.prefix;
 124  
     }
 125  
 
 126  
     /**
 127  
      * Gets the local name.
 128  
      * 
 129  
      * @return the local name
 130  
      */
 131  
     public String getLocalName() {
 132  888702
         return this.localName;
 133  
     }
 134  
 
 135  
     /**
 136  
      * Does this step match any name? (i.e. Is it '*'?)
 137  
      * 
 138  
      * @return true if it matches any name
 139  
      */
 140  
     public boolean isMatchesAnyName() {
 141  192
         return matchesAnyName;
 142  
     }
 143  
 
 144  
     /**
 145  
      * Gets the step as a fully defined XPath.
 146  
      * 
 147  
      * @return the full XPath for this step
 148  
      */
 149  
     public String getText() {
 150  36
         StringBuffer buf = new StringBuffer(64);
 151  36
         buf.append(getAxisName()).append("::");
 152  36
         if (getPrefix() != null && getPrefix().length() > 0) {
 153  0
             buf.append(getPrefix()).append(':');
 154  
         }
 155  36
         return buf.append(getLocalName()).append(super.getText()).toString();
 156  
     }
 157  
 
 158  
     /**
 159  
      * Evaluate the context node set to find the new node set.
 160  
      * <p>
 161  
      * This method overrides the version in <code>DefaultStep</code> for performance.
 162  
      */
 163  
     public List evaluate(Context context) throws JaxenException {
 164  
 
 165  25656
         List contextNodeSet  = context.getNodeSet();
 166  25656
         int contextSize = contextNodeSet.size();
 167  
         // optimize for context size 0
 168  25656
         if (contextSize == 0) {
 169  126
             return Collections.EMPTY_LIST;
 170  
         }
 171  25530
         ContextSupport support = context.getContextSupport();
 172  25530
         IterableAxis iterableAxis = getIterableAxis();
 173  25530
         boolean namedAccess = (!matchesAnyName && iterableAxis.supportsNamedAccess(support));
 174  
         
 175  
         // optimize for context size 1 (common case, avoids lots of object creation)
 176  25530
         if (contextSize == 1) {
 177  24036
             Object contextNode = contextNodeSet.get(0);
 178  24036
             if (namedAccess) {
 179  
                 // get the iterator over the nodes and check it
 180  9792
                 String uri = null;
 181  9792
                 if (hasPrefix) {
 182  228
                     uri = support.translateNamespacePrefixToUri(prefix);
 183  228
                     if (uri == null) {
 184  0
                         throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
 185  
                     }
 186  
                 }
 187  9792
                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
 188  
                                 contextNode, support, localName, prefix, uri);
 189  9792
                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
 190  2712
                     return Collections.EMPTY_LIST;
 191  
                 }
 192  
 
 193  
                 // convert iterator to list for predicate test
 194  
                 // no need to filter as named access guarantees this
 195  7080
                 List newNodeSet = new ArrayList();
 196  33414
                 while (axisNodeIter.hasNext()) {
 197  26334
                     newNodeSet.add(axisNodeIter.next());
 198  
                 }
 199  
                 
 200  
                 // evaluate the predicates
 201  7080
                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
 202  
                 
 203  
             } 
 204  
             else {
 205  
                 // get the iterator over the nodes and check it
 206  14244
                 Iterator axisNodeIter = iterableAxis.iterator(contextNode, support);
 207  14244
                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
 208  2808
                     return Collections.EMPTY_LIST;
 209  
                 }
 210  
 
 211  
                 // run through iterator, filtering using matches()
 212  
                 // adding to list for predicate test
 213  11436
                 List newNodeSet = new ArrayList(contextSize);
 214  2662866
                 while (axisNodeIter.hasNext()) {
 215  2651430
                     Object eachAxisNode = axisNodeIter.next();
 216  2651430
                     if (matches(eachAxisNode, support)) {
 217  62454
                         newNodeSet.add(eachAxisNode);
 218  
                     }
 219  
                 }
 220  
                 
 221  
                 // evaluate the predicates
 222  11436
                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
 223  
             }
 224  
         }
 225  
 
 226  
         // full case
 227  1494
         IdentitySet unique = new IdentitySet();
 228  1494
         List interimSet = new ArrayList(contextSize);
 229  1494
         List newNodeSet = new ArrayList(contextSize);
 230  
         
 231  1494
         if (namedAccess) {
 232  498
             String uri = null;
 233  498
             if (hasPrefix) {
 234  0
                 uri = support.translateNamespacePrefixToUri(prefix);
 235  0
                 if (uri == null) {
 236  0
                     throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
 237  
                 }
 238  
             }
 239  26124
             for (int i = 0; i < contextSize; ++i) {
 240  25626
                 Object eachContextNode = contextNodeSet.get(i);
 241  
 
 242  25626
                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
 243  
                                 eachContextNode, support, localName, prefix, uri);
 244  25626
                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
 245  24420
                     continue;
 246  
                 }
 247  
 
 248  
                 // ensure only one of each node in the result
 249  4392
                 while (axisNodeIter.hasNext()) {
 250  3186
                     Object eachAxisNode = axisNodeIter.next();
 251  3186
                     if (! unique.contains(eachAxisNode)) {
 252  3186
                         unique.add(eachAxisNode);
 253  3186
                         interimSet.add(eachAxisNode);
 254  
                     }
 255  
                 }
 256  
 
 257  
                 // evaluate the predicates
 258  1206
                 newNodeSet.addAll(getPredicateSet().evaluatePredicates(interimSet, support));
 259  1206
                 interimSet.clear();
 260  
             }
 261  
             
 262  
         } else {
 263  79314
             for (int i = 0; i < contextSize; ++i) {
 264  78330
                 Object eachContextNode = contextNodeSet.get(i);
 265  
 
 266  78330
                 Iterator axisNodeIter = axisIterator(eachContextNode, support);
 267  78330
                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
 268  51264
                     continue;
 269  
                 }
 270  
 
 271  
                 /* See jaxen-106. Might be able to optimize this by doing
 272  
                  * specific matching for individual axes. For instance on namespace axis
 273  
                  * we should only get namespace nodes and on attribute axes we only get 
 274  
                  * attribute nodes. Self and parent axes have single members.
 275  
                  * Children, descendant, ancestor, and sibling axes never 
 276  
                  * see any attributes or namespaces
 277  
                  */
 278  
                 
 279  
                 // ensure only unique matching nodes in the result
 280  107352
                 while (axisNodeIter.hasNext()) {
 281  80286
                     Object eachAxisNode = axisNodeIter.next();
 282  
 
 283  80286
                     if (matches(eachAxisNode, support)) {
 284  21294
                         if (! unique.contains(eachAxisNode)) {
 285  21258
                             unique.add(eachAxisNode);
 286  21258
                             interimSet.add(eachAxisNode);
 287  
                         }
 288  
                     }
 289  
                 }
 290  
 
 291  
                 // evaluate the predicates
 292  27066
                 newNodeSet.addAll(getPredicateSet().evaluatePredicates(interimSet, support));
 293  27054
                 interimSet.clear();
 294  
             }
 295  
         }
 296  
         
 297  1482
         return newNodeSet;
 298  
     }
 299  
     
 300  
     /**
 301  
      * Checks whether the node matches this step.
 302  
      * 
 303  
      * @param node  the node to check
 304  
      * @param contextSupport  the context support
 305  
      * @return true if matches
 306  
      * @throws JaxenException 
 307  
      */
 308  
     public boolean matches(Object node, ContextSupport contextSupport) throws JaxenException {
 309  
         
 310  2731716
         Navigator nav  = contextSupport.getNavigator();
 311  2731716
         String myUri = null;
 312  2731716
         String nodeName = null;
 313  2731716
         String nodeUri = null;
 314  
 
 315  2731716
         if (nav.isElement(node)) {
 316  922362
             nodeName = nav.getElementName(node);
 317  922362
             nodeUri = nav.getElementNamespaceUri(node);
 318  
         } 
 319  1809354
         else if (nav.isText(node)) {
 320  1800018
             return false;
 321  
         } 
 322  9336
         else if (nav.isAttribute(node)) {
 323  5274
             if (getAxis() != Axis.ATTRIBUTE) {
 324  6
                 return false;
 325  
             }
 326  5268
             nodeName = nav.getAttributeName(node);
 327  5268
             nodeUri = nav.getAttributeNamespaceUri(node);
 328  
             
 329  
         } 
 330  4062
         else if (nav.isDocument(node)) {
 331  324
             return false;
 332  
         } 
 333  3738
         else if (nav.isNamespace(node)) {
 334  2874
             if (getAxis() != Axis.NAMESPACE) {
 335  
                 // Only works for namespace::*
 336  30
                 return false;
 337  
             }
 338  2844
             nodeName = nav.getNamespacePrefix(node);
 339  
         } 
 340  
         else {
 341  864
             return false;
 342  
         }
 343  
 
 344  930474
         if (hasPrefix) {
 345  402
             myUri = contextSupport.translateNamespacePrefixToUri(this.prefix);
 346  402
             if (myUri == null) {
 347  0
                     throw new UnresolvableException("Cannot resolve namespace prefix '"+this.prefix+"'");
 348  
             }
 349  
         } 
 350  930072
         else if (matchesAnyName) {
 351  41826
             return true;
 352  
         }
 353  
 
 354  
         // If we map to a non-empty namespace and the node does not
 355  
         // or vice-versa, fail-fast.
 356  888648
         if (hasNamespace(myUri) != hasNamespace(nodeUri)) {
 357  174
             return false;
 358  
         }
 359  
         
 360  
         // To fail-fast, we check the equality of
 361  
         // local-names first.  Shorter strings compare
 362  
         // quicker.
 363  888474
         if (matchesAnyName || nodeName.equals(getLocalName())) {
 364  41934
             return matchesNamespaceURIs(myUri, nodeUri);
 365  
         }
 366  
 
 367  846540
         return false;
 368  
     }
 369  
 
 370  
     /**
 371  
      * Checks whether the URI represents a namespace.
 372  
      * 
 373  
      * @param uri  the URI to check
 374  
      * @return true if non-null and non-empty
 375  
      */
 376  
     private boolean hasNamespace(String uri) {
 377  1777296
         return (uri != null && uri.length() > 0);
 378  
     }
 379  
 
 380  
     /**
 381  
      * Compares two namespace URIs, handling null.
 382  
      * 
 383  
      * @param uri1  the first URI
 384  
      * @param uri2  the second URI
 385  
      * @return true if equal, where null==""
 386  
      */
 387  
     protected boolean matchesNamespaceURIs(String uri1, String uri2) {
 388  41934
         if (uri1 == uri2) {
 389  21534
             return true;
 390  
         }
 391  20400
         if (uri1 == null) {
 392  20388
             return (uri2.length() == 0);
 393  
         }
 394  12
         if (uri2 == null) {
 395  0
             return (uri1.length() == 0);
 396  
         }
 397  12
         return uri1.equals(uri2);
 398  
     }
 399  
 
 400  
     /**
 401  
      * Visitor pattern for the step.
 402  
      * 
 403  
      * @param visitor  the visitor object
 404  
      */
 405  
     public void accept(Visitor visitor) {
 406  0
         visitor.visit(this);
 407  0
     }
 408  
     
 409  
     /**
 410  
      * Returns a full information debugging string.
 411  
      * 
 412  
      * @return a debugging string
 413  
      */
 414  
     public String toString() {
 415  12
         return "[(DefaultNameStep): " + getPrefix() + ":" + getLocalName() + "]";
 416  
     }
 417  
 
 418  
 }