Coverage Report - org.jaxen.expr.DefaultNameStep
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultNameStep
96%
132/137
86%
95/110
7.9
 
 1  
 /*
 2  
  $Id$
 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  7616
         super(axis, predicateSet);
 103  
 
 104  7616
         this.prefix = prefix;
 105  7616
         this.localName = localName;
 106  7616
         this.matchesAnyName = "*".equals(localName);
 107  7616
         this.hasPrefix = (this.prefix != null && this.prefix.length() > 0);
 108  7616
     }
 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  304974
         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  8658
         List contextNodeSet  = context.getNodeSet();
 159  8658
         int contextSize = contextNodeSet.size();
 160  
         // optimize for context size 0
 161  8658
         if (contextSize == 0) {
 162  44
             return Collections.EMPTY_LIST;
 163  
         }
 164  8614
         ContextSupport support = context.getContextSupport();
 165  8614
         IterableAxis iterableAxis = getIterableAxis();
 166  8614
         boolean namedAccess = (!matchesAnyName && iterableAxis.supportsNamedAccess(support));
 167  
         
 168  
         // optimize for context size 1 (common case, avoids lots of object creation)
 169  8614
         if (contextSize == 1) {
 170  8054
             Object contextNode = contextNodeSet.get(0);
 171  8054
             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  
                 }
 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  4768
                 Iterator axisNodeIter = iterableAxis.iterator(contextNode, support);
 200  4768
                 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  3832
                 List newNodeSet = new ArrayList(contextSize);
 207  887578
                 while (axisNodeIter.hasNext()) {
 208  883748
                     Object eachAxisNode = axisNodeIter.next();
 209  883748
                     if (matches(eachAxisNode, support)) {
 210  20844
                         newNodeSet.add(eachAxisNode);
 211  
                     }
 212  883746
                 }
 213  
                 
 214  
                 // evaluate the predicates
 215  3830
                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
 216  
             }
 217  
         }
 218  
 
 219  
         // full case
 220  560
         IdentitySet unique = new IdentitySet();
 221  560
         List interimSet = new ArrayList(contextSize);
 222  560
         List newNodeSet = new ArrayList(contextSize);
 223  
         
 224  560
         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  27040
             for (int i = 0; i < contextSize; ++i) {
 266  26652
                 Object eachContextNode = contextNodeSet.get(i);
 267  
 
 268  26652
                 Iterator axisNodeIter = axisIterator(eachContextNode, support);
 269  26652
                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
 270  17392
                     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  36520
                 while (axisNodeIter.hasNext()) {
 283  27260
                     Object eachAxisNode = axisNodeIter.next();
 284  
 
 285  27260
                     if (matches(eachAxisNode, support)) {
 286  7340
                                                 interimSet.add(eachAxisNode);
 287  
                     }
 288  27260
                 }
 289  
 
 290  
                 // evaluate the predicates
 291  9260
                                 List predicateNodes = getPredicateSet().evaluatePredicates(interimSet, support);
 292  
 
 293  
                                 // ensure only one of each node in the result
 294  9256
                                 Iterator predicateNodeIter = predicateNodes.iterator();
 295  12926
                                 while (predicateNodeIter.hasNext())
 296  
                                 {
 297  3670
                                         Object eachPredicateNode = predicateNodeIter.next();
 298  3670
                                         if (! unique.contains(eachPredicateNode))
 299  
                                         {
 300  3652
                                                 unique.add(eachPredicateNode);
 301  3652
                                                 newNodeSet.add(eachPredicateNode);
 302  
                                         }
 303  3670
                                 }
 304  9256
                 interimSet.clear();
 305  
             }
 306  
         }
 307  
         
 308  556
         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  911008
         Navigator nav  = contextSupport.getNavigator();
 322  911008
         String myUri = null;
 323  911008
         String nodeName = null;
 324  911008
         String nodeUri = null;
 325  
 
 326  911008
         if (nav.isElement(node)) {
 327  307724
             nodeName = nav.getElementName(node);
 328  307724
             nodeUri = nav.getElementNamespaceUri(node);
 329  
         } 
 330  603284
         else if (nav.isText(node)) {
 331  600086
             return false;
 332  
         } 
 333  3198
         else if (nav.isAttribute(node)) {
 334  1828
             if (getAxis() != Axis.ATTRIBUTE) {
 335  2
                 return false;
 336  
             }
 337  1826
             nodeName = nav.getAttributeName(node);
 338  1826
             nodeUri = nav.getAttributeNamespaceUri(node);
 339  
             
 340  
         } 
 341  1370
         else if (nav.isDocument(node)) {
 342  108
             return false;
 343  
         } 
 344  1262
         else if (nav.isNamespace(node)) {
 345  966
             if (getAxis() != Axis.NAMESPACE) {
 346  
                 // Only works for namespace::*
 347  10
                 return false;
 348  
             }
 349  956
             nodeName = nav.getNamespacePrefix(node);
 350  
         } 
 351  
         else {
 352  296
             return false;
 353  
         }
 354  
 
 355  310506
         if (hasPrefix) {
 356  142
             myUri = contextSupport.translateNamespacePrefixToUri(this.prefix);
 357  142
             if (myUri == null) {
 358  2
                     throw new UnresolvableException("Cannot resolve namespace prefix '"+this.prefix+"'");
 359  
             }
 360  
         } 
 361  310364
         else if (matchesAnyName) {
 362  14098
             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  296406
         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  296350
         if (matchesAnyName || nodeName.equals(getLocalName())) {
 375  14090
             return matchesNamespaceURIs(myUri, nodeUri);
 376  
         }
 377  
 
 378  282260
         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  592812
         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  14090
         if (uri1 == uri2) {
 400  7288
             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  
 }