Coverage Report - org.jaxen.expr.DefaultNameStep
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultNameStep
96%
136/141
96%
44/46
6.9
 
 1  
 /*
 2  
  $Id: DefaultNameStep.java,v 1.53 2007/04/17 01:26:35 bewins Exp $
 3  
 
 4  
  Copyright 2003 The Werken Company. All Rights Reserved.
 5  
  
 6  
 Redistribution and use in source and binary forms, with or without
 7  
 modification, are permitted provided that the following conditions are
 8  
 met:
 9  
 
 10  
   * Redistributions of source code must retain the above copyright
 11  
     notice, this list of conditions and the following disclaimer.
 12  
 
 13  
   * Redistributions in binary form must reproduce the above copyright
 14  
     notice, this list of conditions and the following disclaimer in the
 15  
     documentation and/or other materials provided with the distribution.
 16  
 
 17  
   * Neither the name of the Jaxen Project nor the names of its
 18  
     contributors may be used to endorse or promote products derived 
 19  
     from this software without specific prior written permission.
 20  
 
 21  
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 22  
 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 23  
 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 24  
 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 25  
 OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 26  
 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 27  
 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 28  
 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 29  
 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 30  
 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 31  
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 32  
 
 33  
  */
 34  
 package org.jaxen.expr;
 35  
 
 36  
 import java.util.ArrayList;
 37  
 import java.util.Collections;
 38  
 import java.util.Iterator;
 39  
 import java.util.List;
 40  
 
 41  
 import org.jaxen.Context;
 42  
 import org.jaxen.ContextSupport;
 43  
 import org.jaxen.JaxenException;
 44  
 import org.jaxen.UnresolvableException;
 45  
 import org.jaxen.Navigator;
 46  
 import org.jaxen.expr.iter.IterableAxis;
 47  
 import org.jaxen.saxpath.Axis;
 48  
 
 49  
 /** 
 50  
  * Expression object that represents any flavor
 51  
  * of name-test steps within an XPath.
 52  
  * <p>
 53  
  * This includes simple steps, such as "foo",
 54  
  * non-default-axis steps, such as "following-sibling::foo"
 55  
  * or "@foo", and namespace-aware steps, such
 56  
  * as "foo:bar".
 57  
  *
 58  
  * @author bob mcwhirter (bob@werken.com)
 59  
  * @author Stephen Colebourne
 60  
  * @deprecated this class will become non-public in the future;
 61  
  *     use the interface instead
 62  
  */
 63  
 public class DefaultNameStep extends DefaultStep implements NameStep {
 64  
     
 65  
     /**
 66  
      * 
 67  
      */
 68  
     private static final long serialVersionUID = 428414912247718390L;
 69  
 
 70  
     /** 
 71  
      * Our prefix, bound through the current Context.
 72  
      * The empty-string ("") if no prefix was specified.
 73  
      * Decidedly NOT-NULL, due to SAXPath constraints.
 74  
      * This is the 'foo' in 'foo:bar'.
 75  
      */
 76  
     private String prefix;
 77  
 
 78  
     /**
 79  
      * Our local-name.
 80  
      * This is the 'bar' in 'foo:bar'.
 81  
      */
 82  
     private String localName;
 83  
 
 84  
     /** Quick flag denoting if the local name was '*' */
 85  
     private boolean matchesAnyName;
 86  
 
 87  
     /** Quick flag denoting if we have a namespace prefix **/
 88  
     private boolean hasPrefix;
 89  
 
 90  
     /**
 91  
      * Constructor.
 92  
      * 
 93  
      * @param axis  the axis to work through
 94  
      * @param prefix  the name prefix
 95  
      * @param localName  the local name
 96  
      * @param predicateSet  the set of predicates
 97  
      */    
 98  
     public DefaultNameStep(IterableAxis axis,
 99  
                            String prefix,
 100  
                            String localName,
 101  
                            PredicateSet predicateSet) {
 102  7548
         super(axis, predicateSet);
 103  
 
 104  7548
         this.prefix = prefix;
 105  7548
         this.localName = localName;
 106  7548
         this.matchesAnyName = "*".equals(localName);
 107  7548
         this.hasPrefix = (this.prefix != null && this.prefix.length() > 0);
 108  7548
     }
 109  
 
 110  
     /**
 111  
      * Gets the namespace prefix.
 112  
      * 
 113  
      * @return the prefix
 114  
      */
 115  
     public String getPrefix() {
 116  11898
         return this.prefix;
 117  
     }
 118  
 
 119  
     /**
 120  
      * Gets the local name.
 121  
      * 
 122  
      * @return the local name
 123  
      */
 124  
     public String getLocalName() {
 125  304822
         return this.localName;
 126  
     }
 127  
 
 128  
     /**
 129  
      * Does this step match any name? (i.e. Is it '*'?)
 130  
      * 
 131  
      * @return true if it matches any name
 132  
      */
 133  
     public boolean isMatchesAnyName() {
 134  64
         return matchesAnyName;
 135  
     }
 136  
 
 137  
     /**
 138  
      * Gets the step as a fully defined XPath.
 139  
      * 
 140  
      * @return the full XPath for this step
 141  
      */
 142  
     public String getText() {
 143  3118
         StringBuffer buf = new StringBuffer(64);
 144  3118
         buf.append(getAxisName()).append("::");
 145  3118
         if (getPrefix() != null && getPrefix().length() > 0) {
 146  152
             buf.append(getPrefix()).append(':');
 147  
         }
 148  3118
         return buf.append(getLocalName()).append(super.getText()).toString();
 149  
     }
 150  
 
 151  
     /**
 152  
      * Evaluate the context node set to find the new node set.
 153  
      * <p>
 154  
      * This method overrides the version in <code>DefaultStep</code> for performance.
 155  
      */
 156  
     public List evaluate(Context context) throws JaxenException {
 157  
 
 158  8598
         List contextNodeSet  = context.getNodeSet();
 159  8598
         int contextSize = contextNodeSet.size();
 160  
         // optimize for context size 0
 161  8598
         if (contextSize == 0) {
 162  44
             return Collections.EMPTY_LIST;
 163  
         }
 164  8554
         ContextSupport support = context.getContextSupport();
 165  8554
         IterableAxis iterableAxis = getIterableAxis();
 166  8554
         boolean namedAccess = (!matchesAnyName && iterableAxis.supportsNamedAccess(support));
 167  
         
 168  
         // optimize for context size 1 (common case, avoids lots of object creation)
 169  8554
         if (contextSize == 1) {
 170  8052
             Object contextNode = contextNodeSet.get(0);
 171  8052
             if (namedAccess) {
 172  
                 // get the iterator over the nodes and check it
 173  3286
                 String uri = null;
 174  3286
                 if (hasPrefix) {
 175  76
                     uri = support.translateNamespacePrefixToUri(prefix);
 176  76
                     if (uri == null) {
 177  0
                         throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
 178  
                     }
 179  
                 }
 180  3286
                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
 181  
                                 contextNode, support, localName, prefix, uri);
 182  3286
                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
 183  910
                     return Collections.EMPTY_LIST;
 184  
                 }
 185  
 
 186  
                 // convert iterator to list for predicate test
 187  
                 // no need to filter as named access guarantees this
 188  2376
                 List newNodeSet = new ArrayList();
 189  11174
                 while (axisNodeIter.hasNext()) {
 190  8798
                     newNodeSet.add(axisNodeIter.next());
 191  8798
                 }
 192  
                 
 193  
                 // evaluate the predicates
 194  2376
                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
 195  
                 
 196  
             } 
 197  
             else {
 198  
                 // get the iterator over the nodes and check it
 199  4766
                 Iterator axisNodeIter = iterableAxis.iterator(contextNode, support);
 200  4766
                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
 201  936
                     return Collections.EMPTY_LIST;
 202  
                 }
 203  
 
 204  
                 // run through iterator, filtering using matches()
 205  
                 // adding to list for predicate test
 206  3830
                 List newNodeSet = new ArrayList(contextSize);
 207  887662
                 while (axisNodeIter.hasNext()) {
 208  883834
                     Object eachAxisNode = axisNodeIter.next();
 209  883834
                     if (matches(eachAxisNode, support)) {
 210  20844
                         newNodeSet.add(eachAxisNode);
 211  
                     }
 212  883832
                 }
 213  
                 
 214  
                 // evaluate the predicates
 215  3828
                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
 216  
             }
 217  
         }
 218  
 
 219  
         // full case
 220  502
         IdentitySet unique = new IdentitySet();
 221  502
         List interimSet = new ArrayList(contextSize);
 222  502
         List newNodeSet = new ArrayList(contextSize);
 223  
         
 224  502
         if (namedAccess) {
 225  168
             String uri = null;
 226  168
             if (hasPrefix) {
 227  0
                 uri = support.translateNamespacePrefixToUri(prefix);
 228  0
                 if (uri == null) {
 229  0
                     throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
 230  
                 }
 231  
             }
 232  8722
             for (int i = 0; i < contextSize; ++i) {
 233  8554
                 Object eachContextNode = contextNodeSet.get(i);
 234  
 
 235  8554
                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
 236  
                                 eachContextNode, support, localName, prefix, uri);
 237  8554
                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
 238  8150
                     continue;
 239  
                 }
 240  
 
 241  1468
                                 while (axisNodeIter.hasNext())
 242  
                                 {
 243  1064
                                         Object eachAxisNode = axisNodeIter.next();
 244  1064
                                         interimSet.add(eachAxisNode);
 245  1064
                                 }
 246  
 
 247  
                                 // evaluate the predicates
 248  404
                                 List predicateNodes = getPredicateSet().evaluatePredicates(interimSet, support);
 249  
 
 250  
                                 // ensure only one of each node in the result
 251  404
                                 Iterator predicateNodeIter = predicateNodes.iterator();
 252  1244
                                 while (predicateNodeIter.hasNext())
 253  
                                 {
 254  840
                                         Object eachPredicateNode = predicateNodeIter.next();
 255  840
                                         if (! unique.contains(eachPredicateNode))
 256  
                                         {
 257  840
                                                 unique.add(eachPredicateNode);
 258  840
                                                 newNodeSet.add(eachPredicateNode);
 259  
                                         }
 260  840
                                 }
 261  404
                                 interimSet.clear();
 262  
                         }
 263  
             
 264  168
         } else {
 265  26450
             for (int i = 0; i < contextSize; ++i) {
 266  26120
                 Object eachContextNode = contextNodeSet.get(i);
 267  
 
 268  26120
                 Iterator axisNodeIter = axisIterator(eachContextNode, support);
 269  26120
                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
 270  17090
                     continue;
 271  
                 }
 272  
 
 273  
                 /* See jaxen-106. Might be able to optimize this by doing
 274  
                  * specific matching for individual axes. For instance on namespace axis
 275  
                  * we should only get namespace nodes and on attribute axes we only get 
 276  
                  * attribute nodes. Self and parent axes have single members.
 277  
                  * Children, descendant, ancestor, and sibling axes never 
 278  
                  * see any attributes or namespaces
 279  
                  */
 280  
                 
 281  
                 // ensure only unique matching nodes in the result
 282  35812
                 while (axisNodeIter.hasNext()) {
 283  26782
                     Object eachAxisNode = axisNodeIter.next();
 284  
 
 285  26782
                     if (matches(eachAxisNode, support)) {
 286  7118
                                                 interimSet.add(eachAxisNode);
 287  
                     }
 288  26782
                 }
 289  
 
 290  
                 // evaluate the predicates
 291  9030
                                 List predicateNodes = getPredicateSet().evaluatePredicates(interimSet, support);
 292  
 
 293  
                                 // ensure only one of each node in the result
 294  9026
                                 Iterator predicateNodeIter = predicateNodes.iterator();
 295  12476
                                 while (predicateNodeIter.hasNext())
 296  
                                 {
 297  3450
                                         Object eachPredicateNode = predicateNodeIter.next();
 298  3450
                                         if (! unique.contains(eachPredicateNode))
 299  
                                         {
 300  3432
                                                 unique.add(eachPredicateNode);
 301  3432
                                                 newNodeSet.add(eachPredicateNode);
 302  
                                         }
 303  3450
                                 }
 304  9026
                 interimSet.clear();
 305  
             }
 306  
         }
 307  
         
 308  498
         return newNodeSet;
 309  
     }
 310  
     
 311  
     /**
 312  
      * Checks whether the node matches this step.
 313  
      * 
 314  
      * @param node  the node to check
 315  
      * @param contextSupport  the context support
 316  
      * @return true if matches
 317  
      * @throws JaxenException 
 318  
      */
 319  
     public boolean matches(Object node, ContextSupport contextSupport) throws JaxenException {
 320  
         
 321  910616
         Navigator nav  = contextSupport.getNavigator();
 322  910616
         String myUri = null;
 323  910616
         String nodeName = null;
 324  910616
         String nodeUri = null;
 325  
 
 326  910616
         if (nav.isElement(node)) {
 327  307498
             nodeName = nav.getElementName(node);
 328  307498
             nodeUri = nav.getElementNamespaceUri(node);
 329  307498
         } 
 330  603118
         else if (nav.isText(node)) {
 331  600006
             return false;
 332  
         } 
 333  3112
         else if (nav.isAttribute(node)) {
 334  1758
             if (getAxis() != Axis.ATTRIBUTE) {
 335  2
                 return false;
 336  
             }
 337  1756
             nodeName = nav.getAttributeName(node);
 338  1756
             nodeUri = nav.getAttributeNamespaceUri(node);
 339  
             
 340  1756
         } 
 341  1354
         else if (nav.isDocument(node)) {
 342  108
             return false;
 343  
         } 
 344  1246
         else if (nav.isNamespace(node)) {
 345  958
             if (getAxis() != Axis.NAMESPACE) {
 346  
                 // Only works for namespace::*
 347  10
                 return false;
 348  
             }
 349  948
             nodeName = nav.getNamespacePrefix(node);
 350  948
         } 
 351  
         else {
 352  288
             return false;
 353  
         }
 354  
 
 355  310202
         if (hasPrefix) {
 356  140
             myUri = contextSupport.translateNamespacePrefixToUri(this.prefix);
 357  140
             if (myUri == null) {
 358  2
                     throw new UnresolvableException("Cannot resolve namespace prefix '"+this.prefix+"'");
 359  
             }
 360  
         } 
 361  310062
         else if (matchesAnyName) {
 362  13946
             return true;
 363  
         }
 364  
 
 365  
         // If we map to a non-empty namespace and the node does not
 366  
         // or vice-versa, fail-fast.
 367  296254
         if (hasNamespace(myUri) != hasNamespace(nodeUri)) {
 368  56
             return false;
 369  
         }
 370  
         
 371  
         // To fail-fast, we check the equality of
 372  
         // local-names first.  Shorter strings compare
 373  
         // quicker.
 374  296198
         if (matchesAnyName || nodeName.equals(getLocalName())) {
 375  14020
             return matchesNamespaceURIs(myUri, nodeUri);
 376  
         }
 377  
 
 378  282178
         return false;
 379  
     }
 380  
 
 381  
     /**
 382  
      * Checks whether the URI represents a namespace.
 383  
      * 
 384  
      * @param uri  the URI to check
 385  
      * @return true if non-null and non-empty
 386  
      */
 387  
     private boolean hasNamespace(String uri) {
 388  592508
         return (uri != null && uri.length() > 0);
 389  
     }
 390  
 
 391  
     /**
 392  
      * Compares two namespace URIs, handling null.
 393  
      * 
 394  
      * @param uri1  the first URI
 395  
      * @param uri2  the second URI
 396  
      * @return true if equal, where null==""
 397  
      */
 398  
     protected boolean matchesNamespaceURIs(String uri1, String uri2) {
 399  14020
         if (uri1 == uri2) {
 400  7218
             return true;
 401  
         }
 402  6802
         if (uri1 == null) {
 403  6798
             return (uri2.length() == 0);
 404  
         }
 405  4
         if (uri2 == null) {
 406  0
             return (uri1.length() == 0);
 407  
         }
 408  4
         return uri1.equals(uri2);
 409  
     }
 410  
     
 411  
     /**
 412  
      * Returns a full information debugging string.
 413  
      * 
 414  
      * @return a debugging string
 415  
      */
 416  
     public String toString() {
 417  6
         String prefix = getPrefix();
 418  6
         String qName = "".equals(prefix) ? getLocalName() : getPrefix() + ":" + getLocalName();
 419  6
         return "[(DefaultNameStep): " +  qName +  "]";
 420  
     }
 421  
 
 422  
 }