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