KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jaxen > expr > DefaultNameStep


1 /*
2  $Id: DefaultNameStep.java,v 1.45 2005/06/01 11:17:42 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 JavaDoc;
49 import java.util.Collections JavaDoc;
50 import java.util.Iterator JavaDoc;
51 import java.util.List JavaDoc;
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  */

73 public class DefaultNameStep extends DefaultStep implements NameStep {
74     
75     /**
76      * Our prefix, bound through the current Context.
77      * The empty-string ("") if no prefix was specified.
78      * Decidedly NOT-NULL, due to SAXPath constraints.
79      * This is the 'foo' in 'foo:bar'.
80      */

81     private String JavaDoc prefix;
82
83     /**
84      * Our local-name.
85      * This is the 'bar' in 'foo:bar'.
86      */

87     private String JavaDoc localName;
88
89     /** Quick flag denoting if the local name was '*' */
90     private boolean matchesAnyName;
91
92     /** Quick flag denoting if we have a namespace prefix **/
93     private boolean hasPrefix;
94
95     /**
96      * Constructor.
97      *
98      * @param axis the axis to work through
99      * @param prefix the name prefix
100      * @param localName the local name
101      * @param predicateSet the set of predicates
102      */

103     public DefaultNameStep(IterableAxis axis,
104                            String JavaDoc prefix,
105                            String JavaDoc localName,
106                            PredicateSet predicateSet) {
107         super(axis, predicateSet);
108
109         this.prefix = prefix;
110         this.localName = localName;
111         this.matchesAnyName = "*".equals(localName);
112         this.hasPrefix = (this.prefix != null && this.prefix.length() > 0);
113     }
114
115     /**
116      * Gets the namespace prefix.
117      *
118      * @return the prefix
119      */

120     public String JavaDoc getPrefix() {
121         return this.prefix;
122     }
123
124     /**
125      * Gets the local name.
126      *
127      * @return the local name
128      */

129     public String JavaDoc getLocalName() {
130         return this.localName;
131     }
132
133     /**
134      * Does this step match any name? (i.e. Is it '*'?)
135      *
136      * @return true if it matches any name
137      */

138     public boolean isMatchesAnyName() {
139         return matchesAnyName;
140     }
141
142     /**
143      * Gets the step as a fully defined XPath.
144      *
145      * @return the full XPath for this step
146      */

147     public String JavaDoc getText() {
148         StringBuffer JavaDoc buf = new StringBuffer JavaDoc(64);
149         buf.append(getAxisName()).append("::");
150         if (getPrefix() != null && getPrefix().length() > 0) {
151             buf.append(getPrefix()).append(':');
152         }
153         return buf.append(getLocalName()).append(super.getText()).toString();
154     }
155
156     /**
157      * Evaluate the context node set to find the new node set.
158      * <p>
159      * This method overrides the version in <code>DefaultStep</code> for performance.
160      */

161     public List JavaDoc evaluate(Context context) throws JaxenException {
162
163         List JavaDoc contextNodeSet = context.getNodeSet();
164         int contextSize = contextNodeSet.size();
165         // optimize for context size 0
166
if (contextSize == 0) {
167             return Collections.EMPTY_LIST;
168         }
169         ContextSupport support = context.getContextSupport();
170         IterableAxis iterableAxis = getIterableAxis();
171         boolean namedAccess = (!matchesAnyName && iterableAxis.supportsNamedAccess(support));
172         
173         // optimize for context size 1 (common case, avoids lots of object creation)
174
if (contextSize == 1) {
175             Object JavaDoc contextNode = contextNodeSet.get(0);
176             if (namedAccess) {
177                 // get the iterator over the nodes and check it
178
String JavaDoc uri = null;
179                 if (hasPrefix) {
180                     uri = support.translateNamespacePrefixToUri(prefix);
181                     if (uri == null) {
182                         throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
183                     }
184                 }
185                 Iterator JavaDoc axisNodeIter = iterableAxis.namedAccessIterator(
186                                 contextNode, support, localName, prefix, uri);
187                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
188                     return Collections.EMPTY_LIST;
189                 }
190
191                 // convert iterator to list for predicate test
192
// no need to filter as named access guarantees this
193
List JavaDoc newNodeSet = new ArrayList JavaDoc();
194                 while (axisNodeIter.hasNext()) {
195                     newNodeSet.add(axisNodeIter.next());
196                 }
197                 
198                 // evaluate the predicates
199
return getPredicateSet().evaluatePredicates(newNodeSet, support);
200                 
201             }
202             else {
203                 // get the iterator over the nodes and check it
204
Iterator JavaDoc axisNodeIter = iterableAxis.iterator(contextNode, support);
205                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
206                     return Collections.EMPTY_LIST;
207                 }
208
209                 // run through iterator, filtering using matches()
210
// adding to list for predicate test
211
List JavaDoc newNodeSet = new ArrayList JavaDoc(contextSize);
212                 while (axisNodeIter.hasNext()) {
213                     Object JavaDoc eachAxisNode = axisNodeIter.next();
214                     if (matches(eachAxisNode, support)) {
215                         newNodeSet.add(eachAxisNode);
216                     }
217                 }
218                 
219                 // evaluate the predicates
220
return getPredicateSet().evaluatePredicates(newNodeSet, support);
221             }
222         }
223
224         // full case
225
IdentitySet unique = new IdentitySet();
226         List JavaDoc interimSet = new ArrayList JavaDoc(contextSize);
227         List JavaDoc newNodeSet = new ArrayList JavaDoc(contextSize);
228         
229         if (namedAccess) {
230             String JavaDoc uri = null;
231             if (hasPrefix) {
232                 uri = support.translateNamespacePrefixToUri(prefix);
233                 if (uri == null) {
234                     throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
235                 }
236             }
237             for (int i = 0; i < contextSize; ++i) {
238                 Object JavaDoc eachContextNode = contextNodeSet.get(i);
239
240                 Iterator JavaDoc axisNodeIter = iterableAxis.namedAccessIterator(
241                                 eachContextNode, support, localName, prefix, uri);
242                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
243                     continue;
244                 }
245
246                 // ensure only one of each node in the result
247
while (axisNodeIter.hasNext()) {
248                     Object JavaDoc eachAxisNode = axisNodeIter.next();
249                     if (! unique.contains(eachAxisNode)) {
250                         unique.add(eachAxisNode);
251                         interimSet.add(eachAxisNode);
252                     }
253                 }
254
255                 // evaluate the predicates
256
newNodeSet.addAll(getPredicateSet().evaluatePredicates(interimSet, support));
257                 interimSet.clear();
258             }
259             
260         } else {
261             for (int i = 0; i < contextSize; ++i) {
262                 Object JavaDoc eachContextNode = contextNodeSet.get(i);
263
264                 Iterator JavaDoc axisNodeIter = axisIterator(eachContextNode, support);
265                 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
266                     continue;
267                 }
268
269                 /* See jaxen-106. Might be able to optimize this by doing
270                  * specific matching for individual axes. For instance on namespace axis
271                  * we should only get namespace nodes and on attribute axes we only get
272                  * attribute nodes. Self and parent axes have single members.
273                  * Children, descendant, ancestor, and sibling axes never
274                  * see any attributes or namespaces
275                  */

276                 
277                 // ensure only unique matching nodes in the result
278
while (axisNodeIter.hasNext()) {
279                     Object JavaDoc eachAxisNode = axisNodeIter.next();
280
281                     if (matches(eachAxisNode, support)) {
282                         if (! unique.contains(eachAxisNode)) {
283                             unique.add(eachAxisNode);
284                             interimSet.add(eachAxisNode);
285                         }
286                     }
287                 }
288
289                 // evaluate the predicates
290
newNodeSet.addAll(getPredicateSet().evaluatePredicates(interimSet, support));
291                 interimSet.clear();
292             }
293         }
294         
295         return newNodeSet;
296     }
297     
298     /**
299      * Checks whether the node matches this step.
300      *
301      * @param node the node to check
302      * @param contextSupport the context support
303      * @return true if matches
304      * @throws JaxenException
305      */

306     public boolean matches(Object JavaDoc node, ContextSupport contextSupport) throws JaxenException {
307         
308         Navigator nav = contextSupport.getNavigator();
309         String JavaDoc myUri = null;
310         String JavaDoc nodeName = null;
311         String JavaDoc nodeUri = null;
312
313         if (nav.isElement(node)) {
314             nodeName = nav.getElementName(node);
315             nodeUri = nav.getElementNamespaceUri(node);
316         }
317         else if (nav.isText(node)) {
318             return false;
319         }
320         else if (nav.isAttribute(node)) {
321             if (getAxis() != Axis.ATTRIBUTE) {
322                 return false;
323             }
324             nodeName = nav.getAttributeName(node);
325             nodeUri = nav.getAttributeNamespaceUri(node);
326             
327         }
328         else if (nav.isDocument(node)) {
329             return false;
330         }
331         else if (nav.isNamespace(node)) {
332             if (getAxis() != Axis.NAMESPACE) {
333                 // Only works for namespace::*
334
return false;
335             }
336             nodeName = nav.getNamespacePrefix(node);
337         }
338         else {
339             return false;
340         }
341
342         if (hasPrefix) {
343             myUri = contextSupport.translateNamespacePrefixToUri(this.prefix);
344             if (myUri == null) {
345                 throw new UnresolvableException("Cannot resolve namespace prefix '"+this.prefix+"'");
346             }
347         }
348         else if (matchesAnyName) {
349             return true;
350         }
351
352         // If we map to a non-empty namespace and the node does not
353
// or vice-versa, fail-fast.
354
if (hasNamespace(myUri) != hasNamespace(nodeUri)) {
355             return false;
356         }
357         
358         // To fail-fast, we check the equality of
359
// local-names first. Shorter strings compare
360
// quicker.
361
if (matchesAnyName || nodeName.equals(getLocalName())) {
362             return matchesNamespaceURIs(myUri, nodeUri);
363         }
364
365         return false;
366     }
367
368     /**
369      * Checks whether the URI represents a namespace.
370      *
371      * @param uri the URI to check
372      * @return true if non-null and non-empty
373      */

374     private boolean hasNamespace(String JavaDoc uri) {
375         return (uri != null && uri.length() > 0);
376     }
377
378     /**
379      * Compares two namespace URIs, handling null.
380      *
381      * @param uri1 the first URI
382      * @param uri2 the second URI
383      * @return true if equal, where null==""
384      */

385     protected boolean matchesNamespaceURIs(String JavaDoc uri1, String JavaDoc uri2) {
386         if (uri1 == uri2) {
387             return true;
388         }
389         if (uri1 == null) {
390             return (uri2.length() == 0);
391         }
392         if (uri2 == null) {
393             return (uri1.length() == 0);
394         }
395         return uri1.equals(uri2);
396     }
397
398     /**
399      * Visitor pattern for the step.
400      *
401      * @param visitor the visitor object
402      */

403     public void accept(Visitor visitor) {
404         visitor.visit(this);
405     }
406     
407     /**
408      * Returns a full information debugging string.
409      *
410      * @return a debugging string
411      */

412     public String JavaDoc toString() {
413         return "[(DefaultNameStep): " + getPrefix() + ":" + getLocalName() + "[" + super.toString() + "]]";
414     }
415
416 }
417
Popular Tags