1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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 */
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 prefix;
82
83 /***
84 * Our local-name.
85 * This is the 'bar' in 'foo:bar'.
86 */
87 private String 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 prefix,
105 String 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 getPrefix() {
121 return this.prefix;
122 }
123
124 /***
125 * Gets the local name.
126 *
127 * @return the local name
128 */
129 public String 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 getText() {
148 StringBuffer buf = new StringBuffer(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 evaluate(Context context) throws JaxenException {
162
163 List contextNodeSet = context.getNodeSet();
164 int contextSize = contextNodeSet.size();
165
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
174 if (contextSize == 1) {
175 Object contextNode = contextNodeSet.get(0);
176 if (namedAccess) {
177
178 String 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 axisNodeIter = iterableAxis.namedAccessIterator(
186 contextNode, support, localName, prefix, uri);
187 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
188 return Collections.EMPTY_LIST;
189 }
190
191
192
193 List newNodeSet = new ArrayList();
194 while (axisNodeIter.hasNext()) {
195 newNodeSet.add(axisNodeIter.next());
196 }
197
198
199 return getPredicateSet().evaluatePredicates(newNodeSet, support);
200
201 }
202 else {
203
204 Iterator axisNodeIter = iterableAxis.iterator(contextNode, support);
205 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
206 return Collections.EMPTY_LIST;
207 }
208
209
210
211 List newNodeSet = new ArrayList(contextSize);
212 while (axisNodeIter.hasNext()) {
213 Object eachAxisNode = axisNodeIter.next();
214 if (matches(eachAxisNode, support)) {
215 newNodeSet.add(eachAxisNode);
216 }
217 }
218
219
220 return getPredicateSet().evaluatePredicates(newNodeSet, support);
221 }
222 }
223
224
225 IdentitySet unique = new IdentitySet();
226 List interimSet = new ArrayList(contextSize);
227 List newNodeSet = new ArrayList(contextSize);
228
229 if (namedAccess) {
230 String 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 eachContextNode = contextNodeSet.get(i);
239
240 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
241 eachContextNode, support, localName, prefix, uri);
242 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
243 continue;
244 }
245
246
247 while (axisNodeIter.hasNext()) {
248 Object eachAxisNode = axisNodeIter.next();
249 if (! unique.contains(eachAxisNode)) {
250 unique.add(eachAxisNode);
251 interimSet.add(eachAxisNode);
252 }
253 }
254
255
256 newNodeSet.addAll(getPredicateSet().evaluatePredicates(interimSet, support));
257 interimSet.clear();
258 }
259
260 } else {
261 for (int i = 0; i < contextSize; ++i) {
262 Object eachContextNode = contextNodeSet.get(i);
263
264 Iterator axisNodeIter = axisIterator(eachContextNode, support);
265 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
266 continue;
267 }
268
269
270
271
272
273
274
275
276
277
278 while (axisNodeIter.hasNext()) {
279 Object 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
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 node, ContextSupport contextSupport) throws JaxenException {
307
308 Navigator nav = contextSupport.getNavigator();
309 String myUri = null;
310 String nodeName = null;
311 String 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
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
353
354 if (hasNamespace(myUri) != hasNamespace(nodeUri)) {
355 return false;
356 }
357
358
359
360
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 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 uri1, String 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 toString() {
413 return "[(DefaultNameStep): " + getPrefix() + ":" + getLocalName() + "[" + super.toString() + "]]";
414 }
415
416 }