Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
DefaultNameStep |
|
| 6.181818181818182;6.182 |
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 | 13158 | super(axis, predicateSet); |
110 | ||
111 | 13158 | this.prefix = prefix; |
112 | 13158 | this.localName = localName; |
113 | 13158 | this.matchesAnyName = "*".equals(localName); |
114 | 13158 | this.hasPrefix = (this.prefix != null && this.prefix.length() > 0); |
115 | 13158 | } |
116 | ||
117 | /** |
|
118 | * Gets the namespace prefix. |
|
119 | * |
|
120 | * @return the prefix |
|
121 | */ |
|
122 | public String getPrefix() { |
|
123 | 276 | return this.prefix; |
124 | } |
|
125 | ||
126 | /** |
|
127 | * Gets the local name. |
|
128 | * |
|
129 | * @return the local name |
|
130 | */ |
|
131 | public String getLocalName() { |
|
132 | 888702 | 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 | 192 | 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 | 36 | StringBuffer buf = new StringBuffer(64); |
151 | 36 | buf.append(getAxisName()).append("::"); |
152 | 36 | if (getPrefix() != null && getPrefix().length() > 0) { |
153 | 0 | buf.append(getPrefix()).append(':'); |
154 | } |
|
155 | 36 | 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 | 25656 | List contextNodeSet = context.getNodeSet(); |
166 | 25656 | int contextSize = contextNodeSet.size(); |
167 | // optimize for context size 0 |
|
168 | 25656 | if (contextSize == 0) { |
169 | 126 | return Collections.EMPTY_LIST; |
170 | } |
|
171 | 25530 | ContextSupport support = context.getContextSupport(); |
172 | 25530 | IterableAxis iterableAxis = getIterableAxis(); |
173 | 25530 | boolean namedAccess = (!matchesAnyName && iterableAxis.supportsNamedAccess(support)); |
174 | ||
175 | // optimize for context size 1 (common case, avoids lots of object creation) |
|
176 | 25530 | if (contextSize == 1) { |
177 | 24036 | Object contextNode = contextNodeSet.get(0); |
178 | 24036 | if (namedAccess) { |
179 | // get the iterator over the nodes and check it |
|
180 | 9792 | String uri = null; |
181 | 9792 | if (hasPrefix) { |
182 | 228 | uri = support.translateNamespacePrefixToUri(prefix); |
183 | 228 | if (uri == null) { |
184 | 0 | throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix); |
185 | } |
|
186 | } |
|
187 | 9792 | Iterator axisNodeIter = iterableAxis.namedAccessIterator( |
188 | contextNode, support, localName, prefix, uri); |
|
189 | 9792 | if (axisNodeIter == null || axisNodeIter.hasNext() == false) { |
190 | 2712 | 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 | 7080 | List newNodeSet = new ArrayList(); |
196 | 33414 | while (axisNodeIter.hasNext()) { |
197 | 26334 | newNodeSet.add(axisNodeIter.next()); |
198 | } |
|
199 | ||
200 | // evaluate the predicates |
|
201 | 7080 | return getPredicateSet().evaluatePredicates(newNodeSet, support); |
202 | ||
203 | } |
|
204 | else { |
|
205 | // get the iterator over the nodes and check it |
|
206 | 14244 | Iterator axisNodeIter = iterableAxis.iterator(contextNode, support); |
207 | 14244 | if (axisNodeIter == null || axisNodeIter.hasNext() == false) { |
208 | 2808 | return Collections.EMPTY_LIST; |
209 | } |
|
210 | ||
211 | // run through iterator, filtering using matches() |
|
212 | // adding to list for predicate test |
|
213 | 11436 | List newNodeSet = new ArrayList(contextSize); |
214 | 2662866 | while (axisNodeIter.hasNext()) { |
215 | 2651430 | Object eachAxisNode = axisNodeIter.next(); |
216 | 2651430 | if (matches(eachAxisNode, support)) { |
217 | 62454 | newNodeSet.add(eachAxisNode); |
218 | } |
|
219 | } |
|
220 | ||
221 | // evaluate the predicates |
|
222 | 11436 | return getPredicateSet().evaluatePredicates(newNodeSet, support); |
223 | } |
|
224 | } |
|
225 | ||
226 | // full case |
|
227 | 1494 | IdentitySet unique = new IdentitySet(); |
228 | 1494 | List interimSet = new ArrayList(contextSize); |
229 | 1494 | List newNodeSet = new ArrayList(contextSize); |
230 | ||
231 | 1494 | if (namedAccess) { |
232 | 498 | String uri = null; |
233 | 498 | if (hasPrefix) { |
234 | 0 | uri = support.translateNamespacePrefixToUri(prefix); |
235 | 0 | if (uri == null) { |
236 | 0 | throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix); |
237 | } |
|
238 | } |
|
239 | 26124 | for (int i = 0; i < contextSize; ++i) { |
240 | 25626 | Object eachContextNode = contextNodeSet.get(i); |
241 | ||
242 | 25626 | Iterator axisNodeIter = iterableAxis.namedAccessIterator( |
243 | eachContextNode, support, localName, prefix, uri); |
|
244 | 25626 | if (axisNodeIter == null || axisNodeIter.hasNext() == false) { |
245 | 24420 | continue; |
246 | } |
|
247 | ||
248 | // ensure only one of each node in the result |
|
249 | 4392 | while (axisNodeIter.hasNext()) { |
250 | 3186 | Object eachAxisNode = axisNodeIter.next(); |
251 | 3186 | if (! unique.contains(eachAxisNode)) { |
252 | 3186 | unique.add(eachAxisNode); |
253 | 3186 | interimSet.add(eachAxisNode); |
254 | } |
|
255 | } |
|
256 | ||
257 | // evaluate the predicates |
|
258 | 1206 | newNodeSet.addAll(getPredicateSet().evaluatePredicates(interimSet, support)); |
259 | 1206 | interimSet.clear(); |
260 | } |
|
261 | ||
262 | } else { |
|
263 | 79314 | for (int i = 0; i < contextSize; ++i) { |
264 | 78330 | Object eachContextNode = contextNodeSet.get(i); |
265 | ||
266 | 78330 | Iterator axisNodeIter = axisIterator(eachContextNode, support); |
267 | 78330 | if (axisNodeIter == null || axisNodeIter.hasNext() == false) { |
268 | 51264 | 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 | 107352 | while (axisNodeIter.hasNext()) { |
281 | 80286 | Object eachAxisNode = axisNodeIter.next(); |
282 | ||
283 | 80286 | if (matches(eachAxisNode, support)) { |
284 | 21294 | if (! unique.contains(eachAxisNode)) { |
285 | 21258 | unique.add(eachAxisNode); |
286 | 21258 | interimSet.add(eachAxisNode); |
287 | } |
|
288 | } |
|
289 | } |
|
290 | ||
291 | // evaluate the predicates |
|
292 | 27066 | newNodeSet.addAll(getPredicateSet().evaluatePredicates(interimSet, support)); |
293 | 27054 | interimSet.clear(); |
294 | } |
|
295 | } |
|
296 | ||
297 | 1482 | 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 | 2731716 | Navigator nav = contextSupport.getNavigator(); |
311 | 2731716 | String myUri = null; |
312 | 2731716 | String nodeName = null; |
313 | 2731716 | String nodeUri = null; |
314 | ||
315 | 2731716 | if (nav.isElement(node)) { |
316 | 922362 | nodeName = nav.getElementName(node); |
317 | 922362 | nodeUri = nav.getElementNamespaceUri(node); |
318 | } |
|
319 | 1809354 | else if (nav.isText(node)) { |
320 | 1800018 | return false; |
321 | } |
|
322 | 9336 | else if (nav.isAttribute(node)) { |
323 | 5274 | if (getAxis() != Axis.ATTRIBUTE) { |
324 | 6 | return false; |
325 | } |
|
326 | 5268 | nodeName = nav.getAttributeName(node); |
327 | 5268 | nodeUri = nav.getAttributeNamespaceUri(node); |
328 | ||
329 | } |
|
330 | 4062 | else if (nav.isDocument(node)) { |
331 | 324 | return false; |
332 | } |
|
333 | 3738 | else if (nav.isNamespace(node)) { |
334 | 2874 | if (getAxis() != Axis.NAMESPACE) { |
335 | // Only works for namespace::* |
|
336 | 30 | return false; |
337 | } |
|
338 | 2844 | nodeName = nav.getNamespacePrefix(node); |
339 | } |
|
340 | else { |
|
341 | 864 | return false; |
342 | } |
|
343 | ||
344 | 930474 | if (hasPrefix) { |
345 | 402 | myUri = contextSupport.translateNamespacePrefixToUri(this.prefix); |
346 | 402 | if (myUri == null) { |
347 | 0 | throw new UnresolvableException("Cannot resolve namespace prefix '"+this.prefix+"'"); |
348 | } |
|
349 | } |
|
350 | 930072 | else if (matchesAnyName) { |
351 | 41826 | 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 | 888648 | if (hasNamespace(myUri) != hasNamespace(nodeUri)) { |
357 | 174 | return false; |
358 | } |
|
359 | ||
360 | // To fail-fast, we check the equality of |
|
361 | // local-names first. Shorter strings compare |
|
362 | // quicker. |
|
363 | 888474 | if (matchesAnyName || nodeName.equals(getLocalName())) { |
364 | 41934 | return matchesNamespaceURIs(myUri, nodeUri); |
365 | } |
|
366 | ||
367 | 846540 | 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 | 1777296 | 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 | 41934 | if (uri1 == uri2) { |
389 | 21534 | return true; |
390 | } |
|
391 | 20400 | if (uri1 == null) { |
392 | 20388 | return (uri2.length() == 0); |
393 | } |
|
394 | 12 | if (uri2 == null) { |
395 | 0 | return (uri1.length() == 0); |
396 | } |
|
397 | 12 | 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 | 0 | visitor.visit(this); |
407 | 0 | } |
408 | ||
409 | /** |
|
410 | * Returns a full information debugging string. |
|
411 | * |
|
412 | * @return a debugging string |
|
413 | */ |
|
414 | public String toString() { |
|
415 | 12 | return "[(DefaultNameStep): " + getPrefix() + ":" + getLocalName() + "]"; |
416 | } |
|
417 | ||
418 | } |