Coverage Report - org.jaxen.expr.DefaultNameStep
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultNameStep
96%
132/137
96%
44/46
6.9
 
 1  
 /*
 2  
  $Id: DefaultNameStep.java 1290 2007-04-17 01:26:35Z bewins $
 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  18934
         super(axis, predicateSet);
 103  
 
 104  18934
         this.prefix = prefix;
 105  18934
         this.localName = localName;
 106  18934
         this.matchesAnyName = "*".equals(localName);
 107  18934
         this.hasPrefix = (this.prefix != null && this.prefix.length() > 0);
 108  18934
     }
 109  
 
 110  
     /**
 111  
      * Gets the namespace prefix.
 112  
      * 
 113  
      * @return the prefix
 114  
      */
 115  
     public String getPrefix() {
 116  29745
         return this.prefix;
 117  
     }
 118  
 
 119  
     /**
 120  
      * Gets the local name.
 121  
      * 
 122  
      * @return the local name
 123  
      */
 124  
     public String getLocalName() {
 125  762303
         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  160
         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  7795
         StringBuffer buf = new StringBuffer(64);
 144  7795
         buf.append(getAxisName()).append("::");
 145  7795
         if (getPrefix() != null && getPrefix().length() > 0) {
 146  380
             buf.append(getPrefix()).append(':');
 147  
         }
 148  7795
         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  21543
         List contextNodeSet  = context.getNodeSet();
 159  21543
         int contextSize = contextNodeSet.size();
 160  
         // optimize for context size 0
 161  21543
         if (contextSize == 0) {
 162  110
             return Collections.EMPTY_LIST;
 163  
         }
 164  21433
         ContextSupport support = context.getContextSupport();
 165  21433
         IterableAxis iterableAxis = getIterableAxis();
 166  21433
         boolean namedAccess = (!matchesAnyName && iterableAxis.supportsNamedAccess(support));
 167  
         
 168  
         // optimize for context size 1 (common case, avoids lots of object creation)
 169  21433
         if (contextSize == 1) {
 170  20130
             Object contextNode = contextNodeSet.get(0);
 171  20130
             if (namedAccess) {
 172  
                 // get the iterator over the nodes and check it
 173  8215
                 String uri = null;
 174  8215
                 if (hasPrefix) {
 175  190
                     uri = support.translateNamespacePrefixToUri(prefix);
 176  190
                     if (uri == null) {
 177  0
                         throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
 178  
                     }
 179  
                 }
 180  8215
                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
 181  
                                 contextNode, support, localName, prefix, uri);
 182  8215
                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
 183  2275
                     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  5940
                 List newNodeSet = new ArrayList();
 189  27935
                 while (axisNodeIter.hasNext()) {
 190  21995
                     newNodeSet.add(axisNodeIter.next());
 191  
                 }
 192  
                 
 193  
                 // evaluate the predicates
 194  5940
                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
 195  
                 
 196  
             } 
 197  
             else {
 198  
                 // get the iterator over the nodes and check it
 199  11915
                 Iterator axisNodeIter = iterableAxis.iterator(contextNode, support);
 200  11915
                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
 201  2340
                     return Collections.EMPTY_LIST;
 202  
                 }
 203  
 
 204  
                 // run through iterator, filtering using matches()
 205  
                 // adding to list for predicate test
 206  9575
                 List newNodeSet = new ArrayList(contextSize);
 207  2218979
                 while (axisNodeIter.hasNext()) {
 208  2209409
                     Object eachAxisNode = axisNodeIter.next();
 209  2209409
                     if (matches(eachAxisNode, support)) {
 210  52110
                         newNodeSet.add(eachAxisNode);
 211  
                     }
 212  2209404
                 }
 213  
                 
 214  
                 // evaluate the predicates
 215  9570
                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
 216  
             }
 217  
         }
 218  
 
 219  
         // full case
 220  1303
         IdentitySet unique = new IdentitySet();
 221  1303
         List interimSet = new ArrayList(contextSize);
 222  1303
         List newNodeSet = new ArrayList(contextSize);
 223  
         
 224  1303
         if (namedAccess) {
 225  420
             String uri = null;
 226  420
             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  21805
             for (int i = 0; i < contextSize; ++i) {
 233  21385
                 Object eachContextNode = contextNodeSet.get(i);
 234  
 
 235  21385
                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
 236  
                                 eachContextNode, support, localName, prefix, uri);
 237  21385
                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
 238  20375
                     continue;
 239  
                 }
 240  
 
 241  3670
                                 while (axisNodeIter.hasNext())
 242  
                                 {
 243  2660
                                         Object eachAxisNode = axisNodeIter.next();
 244  2660
                                         interimSet.add(eachAxisNode);
 245  2660
                                 }
 246  
 
 247  
                                 // evaluate the predicates
 248  1010
                                 List predicateNodes = getPredicateSet().evaluatePredicates(interimSet, support);
 249  
 
 250  
                                 // ensure only one of each node in the result
 251  1010
                                 Iterator predicateNodeIter = predicateNodes.iterator();
 252  3110
                                 while (predicateNodeIter.hasNext())
 253  
                                 {
 254  2100
                                         Object eachPredicateNode = predicateNodeIter.next();
 255  2100
                                         if (! unique.contains(eachPredicateNode))
 256  
                                         {
 257  2100
                                                 unique.add(eachPredicateNode);
 258  2100
                                                 newNodeSet.add(eachPredicateNode);
 259  
                                         }
 260  2100
                                 }
 261  1010
                                 interimSet.clear();
 262  
                         }
 263  
             
 264  420
         } else {
 265  66797
             for (int i = 0; i < contextSize; ++i) {
 266  65924
                 Object eachContextNode = contextNodeSet.get(i);
 267  
 
 268  65924
                 Iterator axisNodeIter = axisIterator(eachContextNode, support);
 269  65924
                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
 270  43013
                     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  90442
                 while (axisNodeIter.hasNext()) {
 283  67531
                     Object eachAxisNode = axisNodeIter.next();
 284  
 
 285  67531
                     if (matches(eachAxisNode, support)) {
 286  17907
                                                 interimSet.add(eachAxisNode);
 287  
                     }
 288  67531
                 }
 289  
 
 290  
                 // evaluate the predicates
 291  22911
                                 List predicateNodes = getPredicateSet().evaluatePredicates(interimSet, support);
 292  
 
 293  
                                 // ensure only one of each node in the result
 294  22901
                                 Iterator predicateNodeIter = predicateNodes.iterator();
 295  31638
                                 while (predicateNodeIter.hasNext())
 296  
                                 {
 297  8737
                                         Object eachPredicateNode = predicateNodeIter.next();
 298  8737
                                         if (! unique.contains(eachPredicateNode))
 299  
                                         {
 300  8692
                                                 unique.add(eachPredicateNode);
 301  8692
                                                 newNodeSet.add(eachPredicateNode);
 302  
                                         }
 303  8737
                                 }
 304  22901
                 interimSet.clear();
 305  
             }
 306  
         }
 307  
         
 308  1293
         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  2276940
         Navigator nav  = contextSupport.getNavigator();
 322  2276940
         String myUri = null;
 323  2276940
         String nodeName = null;
 324  2276940
         String nodeUri = null;
 325  
 
 326  2276940
         if (nav.isElement(node)) {
 327  768993
             nodeName = nav.getElementName(node);
 328  768993
             nodeUri = nav.getElementNamespaceUri(node);
 329  
         } 
 330  1507947
         else if (nav.isText(node)) {
 331  1500167
             return false;
 332  
         } 
 333  7780
         else if (nav.isAttribute(node)) {
 334  4395
             if (getAxis() != Axis.ATTRIBUTE) {
 335  5
                 return false;
 336  
             }
 337  4390
             nodeName = nav.getAttributeName(node);
 338  4390
             nodeUri = nav.getAttributeNamespaceUri(node);
 339  
             
 340  
         } 
 341  3385
         else if (nav.isDocument(node)) {
 342  270
             return false;
 343  
         } 
 344  3115
         else if (nav.isNamespace(node)) {
 345  2395
             if (getAxis() != Axis.NAMESPACE) {
 346  
                 // Only works for namespace::*
 347  25
                 return false;
 348  
             }
 349  2370
             nodeName = nav.getNamespacePrefix(node);
 350  
         } 
 351  
         else {
 352  720
             return false;
 353  
         }
 354  
 
 355  775753
         if (hasPrefix) {
 356  350
             myUri = contextSupport.translateNamespacePrefixToUri(this.prefix);
 357  350
             if (myUri == null) {
 358  5
                     throw new UnresolvableException("Cannot resolve namespace prefix '"+this.prefix+"'");
 359  
             }
 360  
         } 
 361  775403
         else if (matchesAnyName) {
 362  34865
             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  740883
         if (hasNamespace(myUri) != hasNamespace(nodeUri)) {
 368  140
             return false;
 369  
         }
 370  
         
 371  
         // To fail-fast, we check the equality of
 372  
         // local-names first.  Shorter strings compare
 373  
         // quicker.
 374  740743
         if (matchesAnyName || nodeName.equals(getLocalName())) {
 375  35162
             return matchesNamespaceURIs(myUri, nodeUri);
 376  
         }
 377  
 
 378  705581
         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  1481766
         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  35162
         if (uri1 == uri2) {
 400  18157
             return true;
 401  
         }
 402  17005
         if (uri1 == null) {
 403  16995
             return (uri2.length() == 0);
 404  
         }
 405  10
         if (uri2 == null) {
 406  0
             return (uri1.length() == 0);
 407  
         }
 408  10
         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  15
         String prefix = getPrefix();
 418  15
         String qName = "".equals(prefix) ? getLocalName() : getPrefix() + ":" + getLocalName();
 419  15
         return "[(DefaultNameStep): " +  qName +  "]";
 420  
     }
 421  
 
 422  
 }