##### Coverage Report - org.jaxen.expr.DefaultNameStep

Classes in this File | Line Coverage | Branch Coverage | Complexity
DefaultNameStep |
93% |
95% |
| 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 |  |
     }