View Javadoc
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   */
61  public class DefaultNameStep extends DefaultStep implements NameStep {
62      
63      private static final long serialVersionUID = 428414912247718390L;
64  
65      /** 
66       * Our prefix, bound through the current Context.
67       * The empty-string ("") if no prefix was specified.
68       * Decidedly NOT-NULL, due to SAXPath constraints.
69       * This is the 'foo' in 'foo:bar'.
70       */
71      private String prefix;
72  
73      /**
74       * Our local-name.
75       * This is the 'bar' in 'foo:bar'.
76       */
77      private String localName;
78  
79      /** Quick flag denoting if the local name was '*' */
80      private boolean matchesAnyName;
81  
82      /** Quick flag denoting if we have a namespace prefix **/
83      private boolean hasPrefix;
84  
85      /**
86       * Constructor.
87       * 
88       * @param axis  the axis to work through
89       * @param prefix  the name prefix
90       * @param localName  the local name
91       * @param predicateSet  the set of predicates
92       */    
93      public DefaultNameStep(IterableAxis axis,
94                             String prefix,
95                             String localName,
96                             PredicateSet predicateSet) {
97          super(axis, predicateSet);
98  
99          this.prefix = prefix;
100         this.localName = localName;
101         this.matchesAnyName = "*".equals(localName);
102         this.hasPrefix = (this.prefix != null && this.prefix.length() > 0);
103     }
104 
105     /**
106      * Gets the namespace prefix.
107      * 
108      * @return the prefix
109      */
110     public String getPrefix() {
111         return this.prefix;
112     }
113 
114     /**
115      * Gets the local name.
116      * 
117      * @return the local name
118      */
119     public String getLocalName() {
120         return this.localName;
121     }
122 
123     /**
124      * Does this step match any name? (i.e. Is it '*'?)
125      * 
126      * @return true if it matches any name
127      */
128     public boolean isMatchesAnyName() {
129         return matchesAnyName;
130     }
131 
132     /**
133      * Gets the step as a fully defined XPath.
134      * 
135      * @return the full XPath for this step
136      */
137     public String getText() {
138         StringBuffer buf = new StringBuffer(64);
139         buf.append(getAxisName()).append("::");
140         if (getPrefix() != null && getPrefix().length() > 0) {
141             buf.append(getPrefix()).append(':');
142         }
143         return buf.append(getLocalName()).append(super.getText()).toString();
144     }
145 
146     /**
147      * Evaluate the context node set to find the new node set.
148      * <p>
149      * This method overrides the version in <code>DefaultStep</code> for performance.
150      */
151     public List evaluate(Context context) throws JaxenException {
152 
153         List contextNodeSet  = context.getNodeSet();
154         int contextSize = contextNodeSet.size();
155         // optimize for context size 0
156         if (contextSize == 0) {
157             return Collections.EMPTY_LIST;
158         }
159         ContextSupport support = context.getContextSupport();
160         IterableAxis iterableAxis = getIterableAxis();
161         boolean namedAccess = (!matchesAnyName && iterableAxis.supportsNamedAccess(support));
162         
163         // optimize for context size 1 (common case, avoids lots of object creation)
164         if (contextSize == 1) {
165             Object contextNode = contextNodeSet.get(0);
166             if (namedAccess) {
167                 // get the iterator over the nodes and check it
168                 String uri = null;
169                 if (hasPrefix) {
170                     uri = support.translateNamespacePrefixToUri(prefix);
171                     if (uri == null) {
172                         throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
173                     }
174                 }
175                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
176                                 contextNode, support, localName, prefix, uri);
177                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
178                     return Collections.EMPTY_LIST;
179                 }
180 
181                 // convert iterator to list for predicate test
182                 // no need to filter as named access guarantees this
183                 List newNodeSet = new ArrayList();
184                 while (axisNodeIter.hasNext()) {
185                     newNodeSet.add(axisNodeIter.next());
186                 }
187                 
188                 // evaluate the predicates
189                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
190                 
191             } 
192             else {
193                 // get the iterator over the nodes and check it
194                 Iterator axisNodeIter = iterableAxis.iterator(contextNode, support);
195                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
196                     return Collections.EMPTY_LIST;
197                 }
198 
199                 // run through iterator, filtering using matches()
200                 // adding to list for predicate test
201                 List newNodeSet = new ArrayList(contextSize);
202                 while (axisNodeIter.hasNext()) {
203                     Object eachAxisNode = axisNodeIter.next();
204                     if (matches(eachAxisNode, support)) {
205                         newNodeSet.add(eachAxisNode);
206                     }
207                 }
208                 
209                 // evaluate the predicates
210                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
211             }
212         }
213 
214         // full case
215         IdentitySet unique = new IdentitySet();
216         List<Object> interimSet = new ArrayList<Object>(contextSize);
217         List<Object> newNodeSet = new ArrayList<Object>(contextSize);
218         
219         if (namedAccess) {
220             String uri = null;
221             if (hasPrefix) {
222                 uri = support.translateNamespacePrefixToUri(prefix);
223                 if (uri == null) {
224                     throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
225                 }
226             }
227             for (int i = 0; i < contextSize; ++i) {
228                 Object eachContextNode = contextNodeSet.get(i);
229 
230                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
231                                 eachContextNode, support, localName, prefix, uri);
232                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
233                     continue;
234                 }
235 
236                 while (axisNodeIter.hasNext())
237                 {
238                     Object eachAxisNode = axisNodeIter.next();
239                     interimSet.add(eachAxisNode);
240                 }
241 
242                 // evaluate the predicates
243                 List predicateNodes = getPredicateSet().evaluatePredicates(interimSet, support);
244 
245                 // ensure only one of each node in the result
246                 Iterator predicateNodeIter = predicateNodes.iterator();
247                 while (predicateNodeIter.hasNext())
248                 {
249                     Object eachPredicateNode = predicateNodeIter.next();
250                     if (! unique.contains(eachPredicateNode))
251                     {
252                         unique.add(eachPredicateNode);
253                         newNodeSet.add(eachPredicateNode);
254                     }
255                 }
256                 interimSet.clear();
257             }
258             
259         } else {
260             for (int i = 0; i < contextSize; ++i) {
261                 Object eachContextNode = contextNodeSet.get(i);
262 
263                 Iterator axisNodeIter = axisIterator(eachContextNode, support);
264                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
265                     continue;
266                 }
267 
268                 /* See jaxen-106. Might be able to optimize this by doing
269                  * specific matching for individual axes. For instance on namespace axis
270                  * we should only get namespace nodes and on attribute axes we only get 
271                  * attribute nodes. Self and parent axes have single members.
272                  * Children, descendant, ancestor, and sibling axes never 
273                  * see any attributes or namespaces
274                  */
275                 
276                 // ensure only unique matching nodes in the result
277                 while (axisNodeIter.hasNext()) {
278                     Object eachAxisNode = axisNodeIter.next();
279 
280                     if (matches(eachAxisNode, support)) {
281                         interimSet.add(eachAxisNode);
282                     }
283                 }
284 
285                 // evaluate the predicates
286                 List predicateNodes = getPredicateSet().evaluatePredicates(interimSet, support);
287 
288                 // ensure only one of each node in the result
289                 Iterator predicateNodeIter = predicateNodes.iterator();
290                 while (predicateNodeIter.hasNext())
291                 {
292                     Object eachPredicateNode = predicateNodeIter.next();
293                     if (! unique.contains(eachPredicateNode))
294                     {
295                         unique.add(eachPredicateNode);
296                         newNodeSet.add(eachPredicateNode);
297                     }
298                 }
299                 interimSet.clear();
300             }
301         }
302         
303         return newNodeSet;
304     }
305     
306     /**
307      * Checks whether the node matches this step.
308      * 
309      * @param node  the node to check
310      * @param contextSupport  the context support
311      * @return true if matches
312      * @throws JaxenException 
313      */
314     public boolean matches(Object node, ContextSupport contextSupport) throws JaxenException {
315         
316         Navigator nav  = contextSupport.getNavigator();
317         String myUri = null;
318         String nodeName = null;
319         String nodeUri = null;
320 
321         if (nav.isElement(node)) {
322             nodeName = nav.getElementName(node);
323             nodeUri = nav.getElementNamespaceUri(node);
324         } 
325         else if (nav.isText(node)) {
326             return false;
327         } 
328         else if (nav.isAttribute(node)) {
329             if (getAxis() != Axis.ATTRIBUTE) {
330                 return false;
331             }
332             nodeName = nav.getAttributeName(node);
333             nodeUri = nav.getAttributeNamespaceUri(node);
334             
335         } 
336         else if (nav.isDocument(node)) {
337             return false;
338         } 
339         else if (nav.isNamespace(node)) {
340             if (getAxis() != Axis.NAMESPACE) {
341                 // Only works for namespace::*
342                 return false;
343             }
344             nodeName = nav.getNamespacePrefix(node);
345         } 
346         else {
347             return false;
348         }
349 
350         if (hasPrefix) {
351             myUri = contextSupport.translateNamespacePrefixToUri(this.prefix);
352             if (myUri == null) {
353                 throw new UnresolvableException("Cannot resolve namespace prefix '"+this.prefix+"'");
354             }
355         } 
356         else if (matchesAnyName) {
357             return true;
358         }
359 
360         // If we map to a non-empty namespace and the node does not
361         // or vice-versa, fail-fast.
362         if (hasNamespace(myUri) != hasNamespace(nodeUri)) {
363             return false;
364         }
365         
366         // To fail-fast, we check the equality of
367         // local-names first.  Shorter strings compare
368         // quicker.
369         if (matchesAnyName || nodeName.equals(getLocalName())) {
370             return matchesNamespaceURIs(myUri, nodeUri);
371         }
372 
373         return false;
374     }
375 
376     /**
377      * Checks whether the URI represents a namespace.
378      * 
379      * @param uri  the URI to check
380      * @return true if non-null and non-empty
381      */
382     private boolean hasNamespace(String uri) {
383         return (uri != null && uri.length() > 0);
384     }
385 
386     /**
387      * Compares two namespace URIs, handling null.
388      * 
389      * @param uri1  the first URI
390      * @param uri2  the second URI
391      * @return true if equal, where null==""
392      */
393     boolean matchesNamespaceURIs(String uri1, String uri2) {
394         if (uri1 == uri2) {
395             return true;
396         }
397         if (uri1 == null) {
398             return (uri2.length() == 0);
399         }
400         if (uri2 == null) {
401             return (uri1.length() == 0);
402         }
403         return uri1.equals(uri2);
404     }
405     
406     /**
407      * Returns a full information debugging string.
408      * 
409      * @return a debugging string
410      */
411     @Override
412     public String toString() {
413         String prefix = getPrefix();
414         String qName = "".equals(prefix) ? getLocalName() : getPrefix() + ":" + getLocalName();
415         return "[(DefaultNameStep): " +  qName +  "]";
416     }
417 
418 }