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