View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jxpath.ri.axes;
19  
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.List;
23  
24  import org.apache.commons.jxpath.JXPathException;
25  import org.apache.commons.jxpath.ri.Compiler;
26  import org.apache.commons.jxpath.ri.EvalContext;
27  import org.apache.commons.jxpath.ri.InfoSetUtil;
28  import org.apache.commons.jxpath.ri.QName;
29  import org.apache.commons.jxpath.ri.compiler.Expression;
30  import org.apache.commons.jxpath.ri.compiler.NameAttributeTest;
31  import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
32  import org.apache.commons.jxpath.ri.compiler.NodeTest;
33  import org.apache.commons.jxpath.ri.compiler.Step;
34  import org.apache.commons.jxpath.ri.model.NodeIterator;
35  import org.apache.commons.jxpath.ri.model.NodePointer;
36  import org.apache.commons.jxpath.ri.model.beans.LangAttributePointer;
37  import org.apache.commons.jxpath.ri.model.beans.NullElementPointer;
38  import org.apache.commons.jxpath.ri.model.beans.NullPropertyPointer;
39  import org.apache.commons.jxpath.ri.model.beans.PropertyOwnerPointer;
40  import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;
41  
42  /**
43   * An evaluation mechanism for simple XPaths, which is much faster than the usual process. It is only used for xpaths which have no context-dependent parts,
44   * consist entirely of {@code child::name} and {@code self::node()} steps with predicates that either integer or have the form {@code [@name = ...]}.
45   */
46  public class SimplePathInterpreter {
47      // Because of the complexity caused by the variety of situations
48      // that need to be addressed by this class, we attempt to break up
49      // the class into individual methods addressing those situations
50      // individually. The names of the methods are supposed to
51      // give brief descriptions of those situations.
52  
53      private static final QName QNAME_NAME = new QName(null, "name");
54      private static final int PERFECT_MATCH = 1000;
55      // Uncomment this variable and the PATH = ... lines in
56      // the two following methods in order to be able to print the
57      // currently evaluated path for debugging of this class
58  //    private static String PATH;       // Debugging
59  
60      /**
61       * For a pointer that matches an actual node, returns 0. For a pointer that does not match an actual node, but whose parent pointer does returns -1, etc.
62       *
63       * @param pointer input pointer
64       * @return int match quality code
65       */
66      private static int computeQuality(NodePointer pointer) {
67          int quality = PERFECT_MATCH;
68          while (pointer != null && !pointer.isActual()) {
69              quality--;
70              pointer = pointer.getImmediateParentPointer();
71          }
72          return quality;
73      }
74  
75      /**
76       * Create the child pointer for a given step.
77       *
78       * @param parentPointer parent pointer
79       * @param step          associated step
80       * @return NodePointer
81       */
82      private static NodePointer createChildPointerForStep(final PropertyOwnerPointer parentPointer, final Step step) {
83          final int axis = step.getAxis();
84          if (axis == Compiler.AXIS_CHILD || axis == Compiler.AXIS_ATTRIBUTE) {
85              final QName qName = ((NodeNameTest) step.getNodeTest()).getNodeName();
86              if (axis == Compiler.AXIS_ATTRIBUTE && isLangAttribute(qName)) {
87                  return new LangAttributePointer(parentPointer);
88              }
89              if (parentPointer.isValidProperty(qName)) {
90                  final NodePointer childPointer = parentPointer.getPropertyPointer();
91                  ((PropertyPointer) childPointer).setPropertyName(qName.toString());
92                  childPointer.setAttribute(axis == Compiler.AXIS_ATTRIBUTE);
93                  return childPointer;
94              }
95              // invalid property gets nothing, not even a NullPointer
96              return null;
97          }
98          return parentPointer;
99      }
100 
101     /**
102      * Creates a "null pointer" that a) represents the requested path and b) can be used for creation of missing nodes in the path.
103      *
104      * @param context     evaluation context
105      * @param parent      parent pointer
106      * @param steps       path steps
107      * @param currentStep step number
108      * @return NodePointer
109      */
110     public static NodePointer createNullPointer(final EvalContext context, NodePointer parent, final Step[] steps, final int currentStep) {
111         if (currentStep == steps.length) {
112             return parent;
113         }
114         parent = valuePointer(parent);
115         final Step step = steps[currentStep];
116         final int axis = step.getAxis();
117         if (axis == Compiler.AXIS_CHILD || axis == Compiler.AXIS_ATTRIBUTE) {
118             final NullPropertyPointer pointer = new NullPropertyPointer(parent);
119             final QName qName = ((NodeNameTest) step.getNodeTest()).getNodeName();
120             pointer.setPropertyName(qName.toString());
121             pointer.setAttribute(axis == Compiler.AXIS_ATTRIBUTE);
122             parent = pointer;
123         }
124         // else { it is self::node() }
125         final Expression[] predicates = step.getPredicates();
126         return createNullPointerForPredicates(context, parent, steps, currentStep, predicates, 0);
127     }
128 
129     /**
130      * Creates a "null pointer" that starts with predicates.
131      *
132      * @param context          evaluation context
133      * @param parent           parent pointer
134      * @param steps            path steps
135      * @param currentStep      step number
136      * @param predicates       predicates
137      * @param currentPredicate int predicate number
138      * @return NodePointer
139      */
140     private static NodePointer createNullPointerForPredicates(final EvalContext context, NodePointer parent, final Step[] steps, final int currentStep,
141             final Expression[] predicates, final int currentPredicate) {
142         for (int i = currentPredicate; i < predicates.length; i++) {
143             final Expression predicate = predicates[i];
144             if (predicate instanceof NameAttributeTest) {
145                 final String key = keyFromPredicate(context, predicate);
146                 parent = valuePointer(parent);
147                 final NullPropertyPointer pointer = new NullPropertyPointer(parent);
148                 pointer.setNameAttributeValue(key);
149                 parent = pointer;
150             } else {
151                 final int index = indexFromPredicate(context, predicate);
152                 if (parent instanceof NullPropertyPointer) {
153                     parent.setIndex(index);
154                 } else {
155                     parent = new NullElementPointer(parent, index);
156                 }
157             }
158         }
159         // Proceed with the remaining steps
160         return createNullPointer(context, parent, steps, currentStep + 1);
161     }
162 
163     /**
164      * Evaluates predicates and proceeds with the subsequent steps of the path.
165      *
166      * @param context          evaluation context
167      * @param parent           parent pointer
168      * @param steps            path steps
169      * @param currentStep      step number
170      * @param predicates       predicate expressions
171      * @param currentPredicate int predicate number
172      * @return NodePointer
173      */
174     private static NodePointer doPredicate(final EvalContext context, final NodePointer parent, final Step[] steps, final int currentStep,
175             final Expression[] predicates, final int currentPredicate) {
176         if (currentPredicate == predicates.length) {
177             return doStep(context, parent, steps, currentStep + 1);
178         }
179         final Expression predicate = predicates[currentPredicate];
180         if (predicate instanceof NameAttributeTest) { // [@name = key1]
181             return doPredicateName(context, parent, steps, currentStep, predicates, currentPredicate);
182         }
183         // else [index]
184         return doPredicateIndex(context, parent, steps, currentStep, predicates, currentPredicate);
185     }
186 
187     /**
188      * Evaluate a subscript predicate: see if the node is a collection and if the index is inside the collection.
189      *
190      * @param context          evaluation context
191      * @param parent           parent pointer
192      * @param steps            path steps
193      * @param currentStep      step number
194      * @param predicates       predicates
195      * @param currentPredicate int predicate number
196      * @return NodePointer
197      */
198     private static NodePointer doPredicateIndex(final EvalContext context, final NodePointer parent, final Step[] steps, final int currentStep,
199             final Expression[] predicates, final int currentPredicate) {
200         final Expression predicate = predicates[currentPredicate];
201         final int index = indexFromPredicate(context, predicate);
202         NodePointer pointer = parent;
203         if (isCollectionElement(pointer, index)) {
204             pointer = (NodePointer) pointer.clone();
205             pointer.setIndex(index);
206             return doPredicate(context, pointer, steps, currentStep, predicates, currentPredicate + 1);
207         }
208         return createNullPointerForPredicates(context, parent, steps, currentStep, predicates, currentPredicate);
209     }
210 
211     /**
212      * Execute a NameAttributeTest predicate
213      *
214      * @param context          evaluation context
215      * @param parent           parent pointer
216      * @param steps            path steps
217      * @param currentStep      int step number
218      * @param predicates       predicates
219      * @param currentPredicate int predicate number
220      * @return NodePointer
221      */
222     private static NodePointer doPredicateName(final EvalContext context, final NodePointer parent, final Step[] steps, final int currentStep,
223             final Expression[] predicates, final int currentPredicate) {
224         final Expression predicate = predicates[currentPredicate];
225         final String key = keyFromPredicate(context, predicate);
226         NodePointer child = valuePointer(parent);
227         if (child instanceof PropertyOwnerPointer) {
228             final PropertyPointer pointer = ((PropertyOwnerPointer) child).getPropertyPointer();
229             pointer.setPropertyName(key);
230             if (pointer.isActual()) {
231                 return doPredicate(context, pointer, steps, currentStep, predicates, currentPredicate + 1);
232             }
233         } else if (child.isCollection()) {
234             // For each node in the collection, perform the following:
235             // if the node is a property owner, apply this predicate to it;
236             // if the node is a collection, apply this predicate to each elem.;
237             // if the node is not a prop owner or a collection,
238             // see if it has the attribute "name" with the right value,
239             // if so - proceed to the next predicate
240             NodePointer bestMatch = null;
241             int bestQuality = 0;
242             child = (NodePointer) child.clone();
243             final int count = child.getLength();
244             for (int i = 0; i < count; i++) {
245                 child.setIndex(i);
246                 final NodePointer valuePointer = valuePointer(child);
247                 NodePointer pointer;
248                 if (valuePointer instanceof PropertyOwnerPointer || valuePointer.isCollection()) {
249                     pointer = doPredicateName(context, valuePointer, steps, currentStep, predicates, currentPredicate);
250                 } else if (isNameAttributeEqual(valuePointer, key)) {
251                     pointer = doPredicate(context, valuePointer, steps, currentStep, predicates, currentPredicate + 1);
252                 } else {
253                     pointer = null;
254                 }
255                 if (pointer != null) {
256                     final int quality = computeQuality(pointer);
257                     if (quality == PERFECT_MATCH) {
258                         return pointer;
259                     }
260                     if (quality > bestQuality) {
261                         bestMatch = (NodePointer) pointer.clone();
262                         bestQuality = quality;
263                     }
264                 }
265             }
266             if (bestMatch != null) {
267                 return bestMatch;
268             }
269         } else {
270             // If the node is a standard InfoSet node (e.g. DOM Node),
271             // employ doPredicates_standard, which will iterate through
272             // the node's children and apply all predicates
273             final NodePointer found = doPredicatesStandard(context, Collections.singletonList(child), steps, currentStep, predicates, currentPredicate);
274             if (found != null) {
275                 return found;
276             }
277         }
278         // If nothing worked - return a null pointer
279         return createNullPointerForPredicates(context, child, steps, currentStep, predicates, currentPredicate);
280     }
281 
282     /**
283      * Called exclusively for standard InfoSet nodes, e.g. DOM nodes to evaluate predicate sequences like [@name=...][@name=...][index].
284      *
285      * @param context          evaluation context
286      * @param parents          List of parent pointers
287      * @param steps            path steps
288      * @param currentStep      step number
289      * @param predicates       predicates
290      * @param currentPredicate int predicate number
291      * @return NodePointer
292      */
293     private static NodePointer doPredicatesStandard(final EvalContext context, final List<NodePointer> parents, final Step[] steps, final int currentStep,
294             final Expression[] predicates, final int currentPredicate) {
295         if (parents.isEmpty()) {
296             return null;
297         }
298         // If all predicates have been processed, take the first
299         // element from the list of results and proceed to the
300         // remaining steps with that element.
301         if (currentPredicate == predicates.length) {
302             final NodePointer pointer = parents.get(0);
303             return doStep(context, pointer, steps, currentStep + 1);
304         }
305         final Expression predicate = predicates[currentPredicate];
306         if (predicate instanceof NameAttributeTest) {
307             final String key = keyFromPredicate(context, predicate);
308             final List<NodePointer> newList = new ArrayList<>();
309             for (int i = 0; i < parents.size(); i++) {
310                 final NodePointer pointer = parents.get(i);
311                 if (isNameAttributeEqual(pointer, key)) {
312                     newList.add(pointer);
313                 }
314             }
315             if (newList.isEmpty()) {
316                 return null;
317             }
318             return doPredicatesStandard(context, newList, steps, currentStep, predicates, currentPredicate + 1);
319         }
320         // For a subscript, simply take the corresponding
321         // element from the list of results and
322         // proceed to the remaining predicates with that element
323         final int index = indexFromPredicate(context, predicate);
324         if (index < 0 || index >= parents.size()) {
325             return null;
326         }
327         final NodePointer ptr = parents.get(index);
328         return doPredicate(context, ptr, steps, currentStep, predicates, currentPredicate + 1);
329     }
330 
331     /**
332      * Recursive evaluation of a path. The general plan is: Look at the current step, find nodes that match it, iterate over those nodes and for each of them
333      * call doStep again for subsequent steps.
334      *
335      * @param context     evaluation context
336      * @param parent      parent pointer
337      * @param steps       path steps
338      * @param currentStep step number
339      * @return NodePointer
340      */
341     private static NodePointer doStep(final EvalContext context, NodePointer parent, final Step[] steps, final int currentStep) {
342         if (parent == null) {
343             return null;
344         }
345         if (currentStep == steps.length) {
346             // We have reached the end of the list of steps
347             return parent;
348         }
349         // Open all containers
350         parent = valuePointer(parent);
351         final Step step = steps[currentStep];
352         final Expression[] predicates = step.getPredicates();
353         // Divide and conquer: the process is broken out into
354         // four major use cases.
355         // 1. Current step has no predicates and
356         // the root is a property owner (e.g. bean or map)
357         // 2. Current step has predicates and
358         // the root is a property owner (e.g. bean or map)
359         // 3. Current step has no predicates and
360         // the root is an InfoSet standard node (e.g. DOM Node)
361         // 4. Current step has predicates and
362         // the root is an InfoSet standard node (e.g. DOM Node)
363         if (parent instanceof PropertyOwnerPointer) {
364             if (predicates == null || predicates.length == 0) {
365                 return doStepNoPredicatesPropertyOwner(context, (PropertyOwnerPointer) parent, steps, currentStep);
366             }
367             return doStepPredicatesPropertyOwner(context, (PropertyOwnerPointer) parent, steps, currentStep);
368         }
369         if (predicates == null || predicates.length == 0) {
370             return doStepNoPredicatesStandard(context, parent, steps, currentStep);
371         }
372         return doStepPredicatesStandard(context, parent, steps, currentStep);
373     }
374 
375     /**
376      * We have a step that starts with a property owner (bean, map, etc) and has no predicates. The name test of the step may map to a scalar property or to a
377      * collection. If it is a collection, we should apply the tail of the path to each element until we find a match. If we don't find a perfect match, we
378      * should return the "best quality" pointer, which has the longest chain of steps mapping to existing nodes and the shortes tail of Null* pointers.
379      *
380      * @param context       evaluation context
381      * @param parentPointer property owner pointer
382      * @param steps         path steps
383      * @param currentStep   step number
384      * @return NodePointer
385      */
386     private static NodePointer doStepNoPredicatesPropertyOwner(final EvalContext context, final PropertyOwnerPointer parentPointer, final Step[] steps,
387             final int currentStep) {
388         final Step step = steps[currentStep];
389         NodePointer childPointer = createChildPointerForStep(parentPointer, step);
390         if (childPointer == null) {
391             return null;
392         }
393         if (!childPointer.isActual()) {
394             // The property does not exist - create a null pointer.
395             return createNullPointer(context, parentPointer, steps, currentStep);
396         }
397         if (currentStep == steps.length - 1) {
398             // If this is the last step - we are done, we found it
399             return childPointer;
400         }
401         if (childPointer.isCollection()) {
402             // Iterate over all values and
403             // execute remaining steps for each node,
404             // looking for the best quality match
405             int bestQuality = 0;
406             childPointer = (NodePointer) childPointer.clone();
407             NodePointer bestMatch = null;
408             final int count = childPointer.getLength();
409             for (int i = 0; i < count; i++) {
410                 childPointer.setIndex(i);
411                 final NodePointer pointer = doStep(context, childPointer, steps, currentStep + 1);
412                 final int quality = computeQuality(pointer);
413                 if (quality == PERFECT_MATCH) {
414                     return pointer;
415                 }
416                 if (quality > bestQuality) {
417                     bestQuality = quality;
418                     bestMatch = (NodePointer) pointer.clone();
419                 }
420             }
421             if (bestMatch != null) {
422                 return bestMatch;
423             }
424             // This step did not find anything - return a null pointer
425             return createNullPointer(context, childPointer, steps, currentStep);
426         }
427         // Evaluate subsequent steps
428         return doStep(context, childPointer, steps, currentStep + 1);
429     }
430 
431     /**
432      * A path that starts with a standard InfoSet node (e.g. DOM Node) and has no predicates. Get a child iterator and apply the tail of the path to each
433      * element until we find a match. If we don't find a perfect match, we should return the "best quality" pointer, which has the longest chain of steps
434      * mapping to existing nodes and the shortes tail of Null* pointers.
435      *
436      * @param context       evaluation context
437      * @param parentPointer parent pointer
438      * @param steps         path steps
439      * @param currentStep   step number
440      * @return NodePointer
441      */
442     private static NodePointer doStepNoPredicatesStandard(final EvalContext context, final NodePointer parentPointer, final Step[] steps,
443             final int currentStep) {
444         final Step step = steps[currentStep];
445         if (step.getAxis() == Compiler.AXIS_SELF) {
446             return doStep(context, parentPointer, steps, currentStep + 1);
447         }
448         int bestQuality = 0;
449         NodePointer bestMatch = null;
450         final NodeIterator it = getNodeIterator(context, parentPointer, step);
451         if (it != null) {
452             for (int i = 1; it.setPosition(i); i++) {
453                 final NodePointer childPointer = it.getNodePointer();
454                 if (steps.length == currentStep + 1) {
455                     // If this is the last step - we are done, we found it
456                     return childPointer;
457                 }
458                 final NodePointer pointer = doStep(context, childPointer, steps, currentStep + 1);
459                 final int quality = computeQuality(pointer);
460                 if (quality == PERFECT_MATCH) {
461                     return pointer;
462                 }
463                 if (quality > bestQuality) {
464                     bestQuality = quality;
465                     bestMatch = (NodePointer) pointer.clone();
466                 }
467             }
468         }
469         return bestMatch != null ? bestMatch : createNullPointer(context, parentPointer, steps, currentStep);
470     }
471 
472     /**
473      * A path that starts with a property owner. The method evaluates the first predicate in a special way and then forwards to a general predicate processing
474      * method.
475      *
476      * @param context       evaluation context
477      * @param parentPointer parent pointer
478      * @param steps         path steps
479      * @param currentStep   step number
480      * @return NodePointer
481      */
482     private static NodePointer doStepPredicatesPropertyOwner(final EvalContext context, final PropertyOwnerPointer parentPointer, final Step[] steps,
483             final int currentStep) {
484         final Step step = steps[currentStep];
485         final Expression[] predicates = step.getPredicates();
486         final NodePointer childPointer = createChildPointerForStep(parentPointer, step);
487         if (!childPointer.isActual()) {
488             // Property does not exist - return a null pointer
489             return createNullPointer(context, parentPointer, steps, currentStep);
490         }
491         // Evaluate predicates
492         return doPredicate(context, childPointer, steps, currentStep, predicates, 0);
493     }
494 
495     /**
496      * A path that starts with a standard InfoSet node, e.g. a DOM Node. The method evaluates the first predicate in a special way and then forwards to a
497      * general predicate processing method.
498      *
499      * @param context     evaluation context
500      * @param parent      parent pointer
501      * @param steps       path steps
502      * @param currentStep step number
503      * @return NodePointer
504      */
505     private static NodePointer doStepPredicatesStandard(final EvalContext context, final NodePointer parent, final Step[] steps, final int currentStep) {
506         final Step step = steps[currentStep];
507         final Expression[] predicates = step.getPredicates();
508         final int axis = step.getAxis();
509         if (axis == Compiler.AXIS_SELF) {
510             return doPredicate(context, parent, steps, currentStep, predicates, 0);
511         }
512         final Expression predicate = predicates[0];
513         // Optimize for a single predicate to avoid building a list
514         // and to allow the direct access to the index'th element
515         // in the case of a simple subscript predecate
516         // It is a very common use case, so it deserves individual
517         // attention
518         if (predicates.length == 1) {
519             final NodeIterator it = getNodeIterator(context, parent, step);
520             NodePointer pointer = null;
521             if (it != null) {
522                 if (predicate instanceof NameAttributeTest) { // [@name = key]
523                     final String key = keyFromPredicate(context, predicate);
524                     for (int i = 1; it.setPosition(i); i++) {
525                         final NodePointer ptr = it.getNodePointer();
526                         if (isNameAttributeEqual(ptr, key)) {
527                             pointer = ptr;
528                             break;
529                         }
530                     }
531                 } else {
532                     final int index = indexFromPredicate(context, predicate);
533                     if (it.setPosition(index + 1)) {
534                         pointer = it.getNodePointer();
535                     }
536                 }
537             }
538             if (pointer != null) {
539                 return doStep(context, pointer, steps, currentStep + 1);
540             }
541         } else {
542             final NodeIterator it = getNodeIterator(context, parent, step);
543             if (it != null) {
544                 final List<NodePointer> list = new ArrayList<>();
545                 for (int i = 1; it.setPosition(i); i++) {
546                     list.add(it.getNodePointer());
547                 }
548                 final NodePointer pointer = doPredicatesStandard(context, list, steps, currentStep, predicates, 0);
549                 if (pointer != null) {
550                     return pointer;
551                 }
552             }
553         }
554         return createNullPointer(context, parent, steps, currentStep);
555     }
556 
557     /**
558      * Gets a NodeIterator.
559      *
560      * @param context evaluation context
561      * @param pointer owning pointer
562      * @param step    triggering step
563      * @return NodeIterator
564      */
565     private static NodeIterator getNodeIterator(final EvalContext context, final NodePointer pointer, final Step step) {
566         if (step.getAxis() == Compiler.AXIS_CHILD) {
567             NodeTest nodeTest = step.getNodeTest();
568             final QName qname = ((NodeNameTest) nodeTest).getNodeName();
569             final String prefix = qname.getPrefix();
570             if (prefix != null) {
571                 final String namespaceURI = context.getJXPathContext().getNamespaceURI(prefix);
572                 nodeTest = new NodeNameTest(qname, namespaceURI);
573             }
574             return pointer.childIterator(nodeTest, false, null);
575         }
576         // else Compiler.AXIS_ATTRIBUTE
577         if (!(step.getNodeTest() instanceof NodeNameTest)) {
578             throw new UnsupportedOperationException("Not supported node test for attributes: " + step.getNodeTest());
579         }
580         return pointer.attributeIterator(((NodeNameTest) step.getNodeTest()).getNodeName());
581     }
582 
583     /**
584      * Extract an integer from a subscript predicate. The returned index starts with 0, even though the subscript starts with 1.
585      *
586      * @param context   evaluation context
587      * @param predicate to evaluate
588      * @return calculated index
589      */
590     private static int indexFromPredicate(final EvalContext context, final Expression predicate) {
591         Object value = predicate.computeValue(context);
592         if (value instanceof EvalContext) {
593             value = ((EvalContext) value).getSingleNodePointer();
594         }
595         if (value instanceof NodePointer) {
596             value = ((NodePointer) value).getValue();
597         }
598         if (value == null) {
599             throw new JXPathException("Predicate value is null: " + predicate);
600         }
601         if (value instanceof Number) {
602             final double round = 0.5;
603             return (int) (InfoSetUtil.doubleValue(value) + round) - 1;
604         }
605         return InfoSetUtil.booleanValue(value) ? 0 : -1;
606     }
607 
608     /**
609      * Interpret the steps of a simple expression path that starts with the given root, which is the result of evaluation of the root expression of the
610      * expression path, applies the given predicates to it and then follows the given steps. All steps must have the axis "child::" or "attribute::" and a name
611      * test. They can also optionally have predicates of type [@name=...] or simply [...] interpreted as an index.
612      *
613      * @param context    evaluation context
614      * @param root       root pointer
615      * @param predicates predicates corresponding to {@code steps}
616      * @param steps      path steps
617      * @return NodePointer
618      */
619     public static NodePointer interpretSimpleExpressionPath(final EvalContext context, final NodePointer root, final Expression[] predicates,
620             final Step[] steps) {
621 //        PATH = createNullPointerForPredicates(context, root,
622 //                    steps, -1, predicates, 0).toString();  // Debugging
623         final NodePointer pointer = doPredicate(context, root, steps, -1, predicates, 0);
624 //        return valuePointer(pointer);
625         return pointer;
626     }
627 
628     /**
629      * Interpret a simple path that starts with the given root and follows the given steps. All steps must have the axis "child::" and a name test. They can
630      * also optionally have predicates of type [@name=expression] or simply [expression] interpreted as an index.
631      *
632      * @param context evaluation context
633      * @param root    root pointer
634      * @param steps   path steps
635      * @return NodePointer
636      */
637     public static NodePointer interpretSimpleLocationPath(final EvalContext context, final NodePointer root, final Step[] steps) {
638 //        PATH = createNullPointer(context, root, steps, 0).toString();  // Dbg
639         final NodePointer pointer = doStep(context, root, steps, 0);
640 //        return valuePointer(pointer);
641         return pointer;
642     }
643 
644     /**
645      * Returns true if the pointer is a collection and the index is withing the bounds of the collection.
646      *
647      * @param pointer input pointer
648      * @param index   to check
649      * @return boolean
650      */
651     private static boolean isCollectionElement(final NodePointer pointer, final int index) {
652         return pointer.isActual() && (index == 0 || pointer.isCollection() && index >= 0 && index < pointer.getLength());
653     }
654 
655     /**
656      * Tests whether {@code name} is a lang attribute.
657      *
658      * @param qName to compare
659      * @return boolean
660      */
661     private static boolean isLangAttribute(final QName qName) {
662         return qName.getPrefix() != null && qName.getPrefix().equals("xml") && qName.getName().equals("lang");
663     }
664 
665     /**
666      * Returns true if the pointer has an attribute called "name" and its value is equal to the supplied string.
667      *
668      * @param pointer input pointer
669      * @param name    name to check
670      * @return boolean
671      */
672     private static boolean isNameAttributeEqual(final NodePointer pointer, final String name) {
673         final NodeIterator it = pointer.attributeIterator(QNAME_NAME);
674         return it != null && it.setPosition(1) && name.equals(it.getNodePointer().getValue());
675     }
676 
677     /**
678      * Extracts the string value of the expression from a predicate like [@name=expression].
679      *
680      * @param context   evaluation context
681      * @param predicate predicate to evaluate
682      * @return String key extracted
683      */
684     private static String keyFromPredicate(final EvalContext context, final Expression predicate) {
685         final Expression expr = ((NameAttributeTest) predicate).getNameTestExpression();
686         return InfoSetUtil.stringValue(expr.computeValue(context));
687     }
688 
689     /**
690      * For an intermediate pointer (e.g. PropertyPointer, ContainerPointer) returns a pointer for the contained value.
691      *
692      * @param pointer input pointer
693      * @return NodePointer
694      */
695     private static NodePointer valuePointer(final NodePointer pointer) {
696         return pointer == null ? null : pointer.getValuePointer();
697     }
698 
699     /**
700      * Constructs a new instance.
701      *
702      * @deprecated Will be private in the next major version.
703      */
704     @Deprecated
705     public SimplePathInterpreter() {
706         // empty
707     }
708 }