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 }