View Javadoc

    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         super(axis, predicateSet);
    110
    111         this.prefix = prefix;
    112         this.localName = localName;
    113         this.matchesAnyName = "*".equals(localName);
    114         this.hasPrefix = (this.prefix != null && this.prefix.length() > 0);
    115     }
    116
    117     /***
    118      * Gets the namespace prefix.
    119      *
    120      * @return the prefix
    121      */
    122     public String getPrefix() {
    123         return this.prefix;
    124     }
    125
    126     /***
    127      * Gets the local name.
    128      *
    129      * @return the local name
    130      */
    131     public String getLocalName() {
    132         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         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         StringBuffer buf = new StringBuffer(64);
    151         buf.append(getAxisName()).append("::");
    152         if (getPrefix() != null && getPrefix().length() > 0) {
    153             buf.append(getPrefix()).append(':');
    154         }
    155         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         List contextNodeSet  = context.getNodeSet();
    166         int contextSize = contextNodeSet.size();
    167         // optimize for context size 0
    168         if (contextSize == 0) {
    169             return Collections.EMPTY_LIST;
    170         }
    171         ContextSupport support = context.getContextSupport();
    172         IterableAxis iterableAxis = getIterableAxis();
    173         boolean namedAccess = (!matchesAnyName && iterableAxis.supportsNamedAccess(support));
    174
    175         // optimize for context size 1 (common case, avoids lots of object creation)
    176         if (contextSize == 1) {
    177             Object contextNode = contextNodeSet.get(0);
    178             if (namedAccess) {
    179                 // get the iterator over the nodes and check it
    180                 String uri = null;
    181                 if (hasPrefix) {
    182                     uri = support.translateNamespacePrefixToUri(prefix);
    183                     if (uri == null) {
    184                         throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
    185                     }
    186                 }
    187                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
    188                                 contextNode, support, localName, prefix, uri);
    189                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
    190                     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                 List newNodeSet = new ArrayList();
    196                 while (axisNodeIter.hasNext()) {
    197                     newNodeSet.add(axisNodeIter.next());
    198                 }
    199
    200                 // evaluate the predicates
    201                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
    202
    203             }
    204             else {
    205                 // get the iterator over the nodes and check it
    206                 Iterator axisNodeIter = iterableAxis.iterator(contextNode, support);
    207                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
    208                     return Collections.EMPTY_LIST;
    209                 }
    210
    211                 // run through iterator, filtering using matches()
    212                 // adding to list for predicate test
    213                 List newNodeSet = new ArrayList(contextSize);
    214                 while (axisNodeIter.hasNext()) {
    215                     Object eachAxisNode = axisNodeIter.next();
    216                     if (matches(eachAxisNode, support)) {
    217                         newNodeSet.add(eachAxisNode);
    218                     }
    219                 }
    220
    221                 // evaluate the predicates
    222                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
    223             }
    224         }
    225
    226         // full case
    227         IdentitySet unique = new IdentitySet();
    228         List interimSet = new ArrayList(contextSize);
    229         List newNodeSet = new ArrayList(contextSize);
    230
    231         if (namedAccess) {
    232             String uri = null;
    233             if (hasPrefix) {
    234                 uri = support.translateNamespacePrefixToUri(prefix);
    235                 if (uri == null) {
    236                     throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
    237                 }
    238             }
    239             for (int i = 0; i < contextSize; ++i) {
    240                 Object eachContextNode = contextNodeSet.get(i);
    241
    242                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
    243                                 eachContextNode, support, localName, prefix, uri);
    244                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
    245                     continue;
    246                 }
    247
    248                 // ensure only one of each node in the result
    249                 while (axisNodeIter.hasNext()) {
    250                     Object eachAxisNode = axisNodeIter.next();
    251                     if (! unique.contains(eachAxisNode)) {
    252                         unique.add(eachAxisNode);
    253                         interimSet.add(eachAxisNode);
    254                     }
    255                 }
    256
    257                 // evaluate the predicates
    258                 newNodeSet.addAll(getPredicateSet().evaluatePredicates(interimSet, support));
    259                 interimSet.clear();
    260             }
    261
    262         } else {
    263             for (int i = 0; i < contextSize; ++i) {
    264                 Object eachContextNode = contextNodeSet.get(i);
    265
    266                 Iterator axisNodeIter = axisIterator(eachContextNode, support);
    267                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
    268                     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                 while (axisNodeIter.hasNext()) {
    281                     Object eachAxisNode = axisNodeIter.next();
    282
    283                     if (matches(eachAxisNode, support)) {
    284                         if (! unique.contains(eachAxisNode)) {
    285                             unique.add(eachAxisNode);
    286                             interimSet.add(eachAxisNode);
    287                         }
    288                     }
    289                 }
    290
    291                 // evaluate the predicates
    292                 newNodeSet.addAll(getPredicateSet().evaluatePredicates(interimSet, support));
    293                 interimSet.clear();
    294             }
    295         }
    296
    297         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         Navigator nav  = contextSupport.getNavigator();
    311         String myUri = null;
    312         String nodeName = null;
    313         String nodeUri = null;
    314
    315         if (nav.isElement(node)) {
    316             nodeName = nav.getElementName(node);
    317             nodeUri = nav.getElementNamespaceUri(node);
    318         }
    319         else if (nav.isText(node)) {
    320             return false;
    321         }
    322         else if (nav.isAttribute(node)) {
    323             if (getAxis() != Axis.ATTRIBUTE) {
    324                 return false;
    325             }
    326             nodeName = nav.getAttributeName(node);
    327             nodeUri = nav.getAttributeNamespaceUri(node);
    328
    329         }
    330         else if (nav.isDocument(node)) {
    331             return false;
    332         }
    333         else if (nav.isNamespace(node)) {
    334             if (getAxis() != Axis.NAMESPACE) {
    335                 // Only works for namespace::*
    336                 return false;
    337             }
    338             nodeName = nav.getNamespacePrefix(node);
    339         }
    340         else {
    341             return false;
    342         }
    343
    344         if (hasPrefix) {
    345             myUri = contextSupport.translateNamespacePrefixToUri(this.prefix);
    346             if (myUri == null) {
    347             	throw new UnresolvableException("Cannot resolve namespace prefix '"+this.prefix+"'");
    348             }
    349         }
    350         else if (matchesAnyName) {
    351             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         if (hasNamespace(myUri) != hasNamespace(nodeUri)) {
    357             return false;
    358         }
    359
    360         // To fail-fast, we check the equality of
    361         // local-names first.  Shorter strings compare
    362         // quicker.
    363         if (matchesAnyName || nodeName.equals(getLocalName())) {
    364             return matchesNamespaceURIs(myUri, nodeUri);
    365         }
    366
    367         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         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         if (uri1 == uri2) {
    389             return true;
    390         }
    391         if (uri1 == null) {
    392             return (uri2.length() == 0);
    393         }
    394         if (uri2 == null) {
    395             return (uri1.length() == 0);
    396         }
    397         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         visitor.visit(this);
    407     }
    408
    409     /***
    410      * Returns a full information debugging string.
    411      *
    412      * @return a debugging string
    413      */
    414     public String toString() {
    415         return "[(DefaultNameStep): " + getPrefix() + ":" + getLocalName() + "]";
    416     }
    417
    418 }