View Javadoc

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         super(axis, predicateSet);
103 
104         this.prefix = prefix;
105         this.localName = localName;
106         this.matchesAnyName = "*".equals(localName);
107         this.hasPrefix = (this.prefix != null && this.prefix.length() > 0);
108     }
109 
110     /**
111      * Gets the namespace prefix.
112      * 
113      * @return the prefix
114      */
115     public String getPrefix() {
116         return this.prefix;
117     }
118 
119     /**
120      * Gets the local name.
121      * 
122      * @return the local name
123      */
124     public String getLocalName() {
125         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         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         StringBuffer buf = new StringBuffer(64);
144         buf.append(getAxisName()).append("::");
145         if (getPrefix() != null && getPrefix().length() > 0) {
146             buf.append(getPrefix()).append(':');
147         }
148         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         List contextNodeSet  = context.getNodeSet();
159         int contextSize = contextNodeSet.size();
160         // optimize for context size 0
161         if (contextSize == 0) {
162             return Collections.EMPTY_LIST;
163         }
164         ContextSupport support = context.getContextSupport();
165         IterableAxis iterableAxis = getIterableAxis();
166         boolean namedAccess = (!matchesAnyName && iterableAxis.supportsNamedAccess(support));
167         
168         // optimize for context size 1 (common case, avoids lots of object creation)
169         if (contextSize == 1) {
170             Object contextNode = contextNodeSet.get(0);
171             if (namedAccess) {
172                 // get the iterator over the nodes and check it
173                 String uri = null;
174                 if (hasPrefix) {
175                     uri = support.translateNamespacePrefixToUri(prefix);
176                     if (uri == null) {
177                         throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
178                     }
179                 }
180                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
181                                 contextNode, support, localName, prefix, uri);
182                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
183                     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                 List newNodeSet = new ArrayList();
189                 while (axisNodeIter.hasNext()) {
190                     newNodeSet.add(axisNodeIter.next());
191                 }
192                 
193                 // evaluate the predicates
194                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
195                 
196             } 
197             else {
198                 // get the iterator over the nodes and check it
199                 Iterator axisNodeIter = iterableAxis.iterator(contextNode, support);
200                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
201                     return Collections.EMPTY_LIST;
202                 }
203 
204                 // run through iterator, filtering using matches()
205                 // adding to list for predicate test
206                 List newNodeSet = new ArrayList(contextSize);
207                 while (axisNodeIter.hasNext()) {
208                     Object eachAxisNode = axisNodeIter.next();
209                     if (matches(eachAxisNode, support)) {
210                         newNodeSet.add(eachAxisNode);
211                     }
212                 }
213                 
214                 // evaluate the predicates
215                 return getPredicateSet().evaluatePredicates(newNodeSet, support);
216             }
217         }
218 
219         // full case
220         IdentitySet unique = new IdentitySet();
221         List interimSet = new ArrayList(contextSize);
222         List newNodeSet = new ArrayList(contextSize);
223         
224         if (namedAccess) {
225             String uri = null;
226             if (hasPrefix) {
227                 uri = support.translateNamespacePrefixToUri(prefix);
228                 if (uri == null) {
229                     throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
230                 }
231             }
232             for (int i = 0; i < contextSize; ++i) {
233                 Object eachContextNode = contextNodeSet.get(i);
234 
235                 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
236                                 eachContextNode, support, localName, prefix, uri);
237                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
238                     continue;
239                 }
240 
241 				while (axisNodeIter.hasNext())
242 				{
243 					Object eachAxisNode = axisNodeIter.next();
244 					interimSet.add(eachAxisNode);
245 				}
246 
247 				// evaluate the predicates
248 				List predicateNodes = getPredicateSet().evaluatePredicates(interimSet, support);
249 
250 				// ensure only one of each node in the result
251 				Iterator predicateNodeIter = predicateNodes.iterator();
252 				while (predicateNodeIter.hasNext())
253 				{
254 					Object eachPredicateNode = predicateNodeIter.next();
255 					if (! unique.contains(eachPredicateNode))
256 					{
257 						unique.add(eachPredicateNode);
258 						newNodeSet.add(eachPredicateNode);
259 					}
260 				}
261 				interimSet.clear();
262 			}
263             
264         } else {
265             for (int i = 0; i < contextSize; ++i) {
266                 Object eachContextNode = contextNodeSet.get(i);
267 
268                 Iterator axisNodeIter = axisIterator(eachContextNode, support);
269                 if (axisNodeIter == null || !axisNodeIter.hasNext()) {
270                     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                 while (axisNodeIter.hasNext()) {
283                     Object eachAxisNode = axisNodeIter.next();
284 
285                     if (matches(eachAxisNode, support)) {
286 						interimSet.add(eachAxisNode);
287                     }
288                 }
289 
290                 // evaluate the predicates
291 				List predicateNodes = getPredicateSet().evaluatePredicates(interimSet, support);
292 
293 				// ensure only one of each node in the result
294 				Iterator predicateNodeIter = predicateNodes.iterator();
295 				while (predicateNodeIter.hasNext())
296 				{
297 					Object eachPredicateNode = predicateNodeIter.next();
298 					if (! unique.contains(eachPredicateNode))
299 					{
300 						unique.add(eachPredicateNode);
301 						newNodeSet.add(eachPredicateNode);
302 					}
303 				}
304                 interimSet.clear();
305             }
306         }
307         
308         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         Navigator nav  = contextSupport.getNavigator();
322         String myUri = null;
323         String nodeName = null;
324         String nodeUri = null;
325 
326         if (nav.isElement(node)) {
327             nodeName = nav.getElementName(node);
328             nodeUri = nav.getElementNamespaceUri(node);
329         } 
330         else if (nav.isText(node)) {
331             return false;
332         } 
333         else if (nav.isAttribute(node)) {
334             if (getAxis() != Axis.ATTRIBUTE) {
335                 return false;
336             }
337             nodeName = nav.getAttributeName(node);
338             nodeUri = nav.getAttributeNamespaceUri(node);
339             
340         } 
341         else if (nav.isDocument(node)) {
342             return false;
343         } 
344         else if (nav.isNamespace(node)) {
345             if (getAxis() != Axis.NAMESPACE) {
346                 // Only works for namespace::*
347                 return false;
348             }
349             nodeName = nav.getNamespacePrefix(node);
350         } 
351         else {
352             return false;
353         }
354 
355         if (hasPrefix) {
356             myUri = contextSupport.translateNamespacePrefixToUri(this.prefix);
357             if (myUri == null) {
358             	throw new UnresolvableException("Cannot resolve namespace prefix '"+this.prefix+"'");
359             }
360         } 
361         else if (matchesAnyName) {
362             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         if (hasNamespace(myUri) != hasNamespace(nodeUri)) {
368             return false;
369         }
370         
371         // To fail-fast, we check the equality of
372         // local-names first.  Shorter strings compare
373         // quicker.
374         if (matchesAnyName || nodeName.equals(getLocalName())) {
375             return matchesNamespaceURIs(myUri, nodeUri);
376         }
377 
378         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         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         if (uri1 == uri2) {
400             return true;
401         }
402         if (uri1 == null) {
403             return (uri2.length() == 0);
404         }
405         if (uri2 == null) {
406             return (uri1.length() == 0);
407         }
408         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         String prefix = getPrefix();
418         String qName = "".equals(prefix) ? getLocalName() : getPrefix() + ":" + getLocalName();
419         return "[(DefaultNameStep): " +  qName +  "]";
420     }
421 
422 }