View Javadoc

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