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 * @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 super(axis, predicateSet);
110
111 this.prefix = prefix;
112 this.localName = localName;
113 this.matchesAnyName = "*".equals(localName);
114 this.hasPrefix = (this.prefix != null && this.prefix.length() > 0);
115 }
116
117 /***
118 * Gets the namespace prefix.
119 *
120 * @return the prefix
121 */
122 public String getPrefix() {
123 return this.prefix;
124 }
125
126 /***
127 * Gets the local name.
128 *
129 * @return the local name
130 */
131 public String getLocalName() {
132 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 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 StringBuffer buf = new StringBuffer(64);
151 buf.append(getAxisName()).append("::");
152 if (getPrefix() != null && getPrefix().length() > 0) {
153 buf.append(getPrefix()).append(':');
154 }
155 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 List contextNodeSet = context.getNodeSet();
166 int contextSize = contextNodeSet.size();
167
168 if (contextSize == 0) {
169 return Collections.EMPTY_LIST;
170 }
171 ContextSupport support = context.getContextSupport();
172 IterableAxis iterableAxis = getIterableAxis();
173 boolean namedAccess = (!matchesAnyName && iterableAxis.supportsNamedAccess(support));
174
175
176 if (contextSize == 1) {
177 Object contextNode = contextNodeSet.get(0);
178 if (namedAccess) {
179
180 String uri = null;
181 if (hasPrefix) {
182 uri = support.translateNamespacePrefixToUri(prefix);
183 if (uri == null) {
184 throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
185 }
186 }
187 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
188 contextNode, support, localName, prefix, uri);
189 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
190 return Collections.EMPTY_LIST;
191 }
192
193
194
195 List newNodeSet = new ArrayList();
196 while (axisNodeIter.hasNext()) {
197 newNodeSet.add(axisNodeIter.next());
198 }
199
200
201 return getPredicateSet().evaluatePredicates(newNodeSet, support);
202
203 }
204 else {
205
206 Iterator axisNodeIter = iterableAxis.iterator(contextNode, support);
207 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
208 return Collections.EMPTY_LIST;
209 }
210
211
212
213 List newNodeSet = new ArrayList(contextSize);
214 while (axisNodeIter.hasNext()) {
215 Object eachAxisNode = axisNodeIter.next();
216 if (matches(eachAxisNode, support)) {
217 newNodeSet.add(eachAxisNode);
218 }
219 }
220
221
222 return getPredicateSet().evaluatePredicates(newNodeSet, support);
223 }
224 }
225
226
227 IdentitySet unique = new IdentitySet();
228 List interimSet = new ArrayList(contextSize);
229 List newNodeSet = new ArrayList(contextSize);
230
231 if (namedAccess) {
232 String uri = null;
233 if (hasPrefix) {
234 uri = support.translateNamespacePrefixToUri(prefix);
235 if (uri == null) {
236 throw new UnresolvableException("XPath expression uses unbound namespace prefix " + prefix);
237 }
238 }
239 for (int i = 0; i < contextSize; ++i) {
240 Object eachContextNode = contextNodeSet.get(i);
241
242 Iterator axisNodeIter = iterableAxis.namedAccessIterator(
243 eachContextNode, support, localName, prefix, uri);
244 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
245 continue;
246 }
247
248
249 while (axisNodeIter.hasNext()) {
250 Object eachAxisNode = axisNodeIter.next();
251 if (! unique.contains(eachAxisNode)) {
252 unique.add(eachAxisNode);
253 interimSet.add(eachAxisNode);
254 }
255 }
256
257
258 newNodeSet.addAll(getPredicateSet().evaluatePredicates(interimSet, support));
259 interimSet.clear();
260 }
261
262 } else {
263 for (int i = 0; i < contextSize; ++i) {
264 Object eachContextNode = contextNodeSet.get(i);
265
266 Iterator axisNodeIter = axisIterator(eachContextNode, support);
267 if (axisNodeIter == null || axisNodeIter.hasNext() == false) {
268 continue;
269 }
270
271
272
273
274
275
276
277
278
279
280 while (axisNodeIter.hasNext()) {
281 Object eachAxisNode = axisNodeIter.next();
282
283 if (matches(eachAxisNode, support)) {
284 if (! unique.contains(eachAxisNode)) {
285 unique.add(eachAxisNode);
286 interimSet.add(eachAxisNode);
287 }
288 }
289 }
290
291
292 newNodeSet.addAll(getPredicateSet().evaluatePredicates(interimSet, support));
293 interimSet.clear();
294 }
295 }
296
297 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 Navigator nav = contextSupport.getNavigator();
311 String myUri = null;
312 String nodeName = null;
313 String nodeUri = null;
314
315 if (nav.isElement(node)) {
316 nodeName = nav.getElementName(node);
317 nodeUri = nav.getElementNamespaceUri(node);
318 }
319 else if (nav.isText(node)) {
320 return false;
321 }
322 else if (nav.isAttribute(node)) {
323 if (getAxis() != Axis.ATTRIBUTE) {
324 return false;
325 }
326 nodeName = nav.getAttributeName(node);
327 nodeUri = nav.getAttributeNamespaceUri(node);
328
329 }
330 else if (nav.isDocument(node)) {
331 return false;
332 }
333 else if (nav.isNamespace(node)) {
334 if (getAxis() != Axis.NAMESPACE) {
335
336 return false;
337 }
338 nodeName = nav.getNamespacePrefix(node);
339 }
340 else {
341 return false;
342 }
343
344 if (hasPrefix) {
345 myUri = contextSupport.translateNamespacePrefixToUri(this.prefix);
346 if (myUri == null) {
347 throw new UnresolvableException("Cannot resolve namespace prefix '"+this.prefix+"'");
348 }
349 }
350 else if (matchesAnyName) {
351 return true;
352 }
353
354
355
356 if (hasNamespace(myUri) != hasNamespace(nodeUri)) {
357 return false;
358 }
359
360
361
362
363 if (matchesAnyName || nodeName.equals(getLocalName())) {
364 return matchesNamespaceURIs(myUri, nodeUri);
365 }
366
367 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 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 if (uri1 == uri2) {
389 return true;
390 }
391 if (uri1 == null) {
392 return (uri2.length() == 0);
393 }
394 if (uri2 == null) {
395 return (uri1.length() == 0);
396 }
397 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 visitor.visit(this);
407 }
408
409 /***
410 * Returns a full information debugging string.
411 *
412 * @return a debugging string
413 */
414 public String toString() {
415 return "[(DefaultNameStep): " + getPrefix() + ":" + getLocalName() + "]";
416 }
417
418 }