/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.jxpath.ri.axes;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.jxpath.JXPathException;
import org.apache.commons.jxpath.ri.Compiler;
import org.apache.commons.jxpath.ri.EvalContext;
import org.apache.commons.jxpath.ri.InfoSetUtil;
import org.apache.commons.jxpath.ri.QName;
import org.apache.commons.jxpath.ri.compiler.Expression;
import org.apache.commons.jxpath.ri.compiler.NameAttributeTest;
import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
import org.apache.commons.jxpath.ri.compiler.NodeTest;
import org.apache.commons.jxpath.ri.compiler.Step;
import org.apache.commons.jxpath.ri.model.NodeIterator;
import org.apache.commons.jxpath.ri.model.NodePointer;
import org.apache.commons.jxpath.ri.model.beans.LangAttributePointer;
import org.apache.commons.jxpath.ri.model.beans.NullElementPointer;
import org.apache.commons.jxpath.ri.model.beans.NullPropertyPointer;
import org.apache.commons.jxpath.ri.model.beans.PropertyOwnerPointer;
import org.apache.commons.jxpath.ri.model.beans.PropertyPointer;

/**
 * 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, consist entirely of
 * <code>child::name</code> and <code>self::node()</code> steps with
 * predicates that either integer or have the form <code>[@name = ...]</code>.
 *
 * @author Dmitri Plotnikov
 * @version $Revision: 652845 $ $Date: 2008-05-02 12:46:46 -0500 (Fri, 02 May 2008) $
 */
public class SimplePathInterpreter {

    // Because of the complexity caused by the variety of situations
    // that need to be addressed by this class, we attempt to break up
    // the class into individual methods addressing those situations
    // individually.  The names of the methods are supposed to
    // give brief descriptions of those situations.

    private static final QName QNAME_NAME = new QName(null, "name");
    private static final int PERFECT_MATCH = 1000;

    // Uncomment this variable and the PATH = ... lines in
    // the two following methods in order to be able to print the
    // currently evaluated path for debugging of this class
//    private static String PATH;       // Debugging

    /**
     * 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 also optionally have predicates
     * of type [@name=expression] or simply [expression] interpreted
     * as an index.
     * @param context evaluation context
     * @param root root pointer
     * @param steps path steps
     * @return NodePointer
     */
    public static NodePointer interpretSimpleLocationPath(
            EvalContext context, NodePointer root, Step[] steps) {
//        PATH = createNullPointer(context, root, steps, 0).toString();  // Dbg
        NodePointer pointer = doStep(context, root, steps, 0);
//        return valuePointer(pointer);
        return pointer;
    }

    /**
     * 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 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 test.  They can also optionally have predicates
     * of type [@name=...] or simply [...] interpreted as an index.
     * @param context evaluation context
     * @param root root pointer
     * @param predicates predicates corresponding to <code>steps</code>
     * @param steps path steps
     * @return NodePointer
     */
    public static NodePointer interpretSimpleExpressionPath(
                EvalContext context, NodePointer root,
                Expression[] predicates, Step[] steps) {
//        PATH = createNullPointerForPredicates(context, root,
//                    steps, -1, predicates, 0).toString();  // Debugging
        NodePointer pointer =
            doPredicate(context, root, steps, -1, predicates, 0);
//        return valuePointer(pointer);
        return pointer;
    }

    /**
     * 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 call doStep again for subsequent steps.
     * @param context evaluation context
     * @param parent parent pointer
     * @param steps path steps
     * @param currentStep step number
     * @return NodePointer
     */
    private static NodePointer doStep(
            EvalContext context, NodePointer parent,
            Step[] steps, int currentStep) {
        if (parent == null) {
            return null;
        }

        if (currentStep == steps.length) {
            // We have reached the end of the list of steps
            return parent;
        }

        // Open all containers
        parent = valuePointer(parent);

        Step step = steps[currentStep];
        Expression[] predicates = step.getPredicates();

        // Divide and conquer: the process is broken out into
        // four major use cases.
        // 1. Current step has no predicates and
        //    the root is a property owner (e.g. bean or map)
        // 2. Current step has predicates and
        //    the root is a property owner (e.g. bean or map)
        // 3. Current step has no predicates and
        //    the root is an InfoSet standard node (e.g. DOM Node)
        // 4. Current step has predicates and
        //    the root is an InfoSet standard node (e.g. DOM Node)

        if (parent instanceof PropertyOwnerPointer) {
            if (predicates == null || predicates.length == 0) {
                return doStepNoPredicatesPropertyOwner(
                    context,
                    (PropertyOwnerPointer) parent,
                    steps,
                    currentStep);
            }
            return doStepPredicatesPropertyOwner(
                context,
                (PropertyOwnerPointer) parent,
                steps,
                currentStep);
        }
        if (predicates == null || predicates.length == 0) {
            return doStepNoPredicatesStandard(
                context,
                parent,
                steps,
                currentStep);
        }
        return doStepPredicatesStandard(
            context,
            parent,
            steps,
            currentStep);
    }

    /**
     * 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 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 should return the "best quality" pointer, which
     * has the longest chain of steps mapping to existing nodes and the shortes
     * tail of Null* pointers.
     * @param context evaluation context
     * @param parentPointer property owner pointer
     * @param steps path steps
     * @param currentStep step number
     * @return NodePointer
     */
    private static NodePointer doStepNoPredicatesPropertyOwner(
                EvalContext context, PropertyOwnerPointer parentPointer,
                Step[] steps, int currentStep) {
        Step step = steps[currentStep];
        NodePointer childPointer =
            createChildPointerForStep(parentPointer, step);

        if (childPointer == null) {
            return null;
        }
        if (!childPointer.isActual()) {
            // The property does not exist - create a null pointer.
            return createNullPointer(
                context,
                parentPointer,
                steps,
                currentStep);
        }
        if (currentStep == steps.length - 1) {
            // If this is the last step - we are done, we found it
            return childPointer;
        }
        if (childPointer.isCollection()) {
            // Iterate over all values and
            // execute remaining steps for each node,
            // looking for the best quality match
            int bestQuality = 0;
            childPointer = (NodePointer) childPointer.clone();
            NodePointer bestMatch = null;
            int count = childPointer.getLength();
            for (int i = 0; i < count; i++) {
                childPointer.setIndex(i);
                NodePointer pointer =
                    doStep(context, childPointer, steps, currentStep + 1);
                int quality = computeQuality(pointer);
                if (quality == PERFECT_MATCH) {
                    return pointer;
                }
                else if (quality > bestQuality) {
                    bestQuality = quality;
                    bestMatch = (NodePointer) pointer.clone();
                }
            }
            if (bestMatch != null) {
                return bestMatch;
            }
            // This step did not find anything - return a null pointer
            return createNullPointer(context, childPointer, steps, currentStep);
        }
        // Evaluate subsequent steps
        return doStep(context, childPointer, steps, currentStep + 1);
    }

    /**
     * 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 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 mapping to existing nodes and the shortes
     * tail of Null* pointers.
     * @param context evaluation context
     * @param parentPointer parent pointer
     * @param steps path steps
     * @param currentStep step number
     * @return NodePointer
     */
    private static NodePointer doStepNoPredicatesStandard(
                EvalContext context, NodePointer parentPointer,
                Step[] steps, int currentStep) {
        Step step = steps[currentStep];

        if (step.getAxis() == Compiler.AXIS_SELF) {
            return doStep(context, parentPointer, steps, currentStep + 1);
        }

        int bestQuality = 0;
        NodePointer bestMatch = null;
        NodeIterator it = getNodeIterator(context, parentPointer, step);
        if (it != null) {
            for (int i = 1; it.setPosition(i); i++) {
                NodePointer childPointer = it.getNodePointer();
                if (steps.length == currentStep + 1) {
                    // If this is the last step - we are done, we found it
                    return childPointer;
                }
                NodePointer pointer = doStep(
                        context, childPointer, steps, currentStep + 1);
                int quality = computeQuality(pointer);
                if (quality == PERFECT_MATCH) {
                    return pointer;
                }
                if (quality > bestQuality) {
                    bestQuality = quality;
                    bestMatch = (NodePointer) pointer.clone();
                }
            }
        }
        return bestMatch != null ? bestMatch
                : createNullPointer(context, parentPointer, steps, currentStep);
    }

    /**
     * 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 method.
     * @param context evaluation context
     * @param parentPointer parent pointer
     * @param steps path steps
     * @param currentStep step number
     * @return NodePointer
     */
    private static NodePointer doStepPredicatesPropertyOwner(
            EvalContext context, PropertyOwnerPointer parentPointer,
            Step[] steps, int currentStep) {
        Step step = steps[currentStep];
        Expression[] predicates = step.getPredicates();

        NodePointer childPointer =
            createChildPointerForStep(parentPointer, step);
        if (!childPointer.isActual()) {
            // Property does not exist - return a null pointer
            return createNullPointer(
                context,
                parentPointer,
                steps,
                currentStep);
        }

        // Evaluate predicates
        return doPredicate(
            context,
            childPointer,
            steps,
            currentStep,
            predicates,
            0);
    }

    /**
     * Create the child pointer for a given step.
     * @param parentPointer parent pointer
     * @param step associated step
     * @return NodePointer
     */
    private static NodePointer createChildPointerForStep(
                PropertyOwnerPointer parentPointer, Step step) {
        int axis = step.getAxis();
        if (axis == Compiler.AXIS_CHILD || axis == Compiler.AXIS_ATTRIBUTE) {
            QName name = ((NodeNameTest) step.getNodeTest()).getNodeName();
            if (axis == Compiler.AXIS_ATTRIBUTE && isLangAttribute(name)) {
                return new LangAttributePointer(parentPointer);
            }
            if (parentPointer.isValidProperty(name)) {
                NodePointer childPointer = parentPointer.getPropertyPointer();
                ((PropertyPointer) childPointer).setPropertyName(
                        name.toString());
                childPointer.setAttribute(axis == Compiler.AXIS_ATTRIBUTE);
                return childPointer;
            }
            //invalid property gets nothing, not even a NullPointer
            return null;
        }
        return parentPointer;
    }

    /**
     * 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 general predicate processing method.
     * @param context evaluation context
     * @param parent parent pointer
     * @param steps path steps
     * @param currentStep step number
     * @return NodePointer
     */
    private static NodePointer doStepPredicatesStandard(
            EvalContext context, NodePointer parent,
            Step[] steps, int currentStep) {
        Step step = steps[currentStep];
        Expression[] predicates = step.getPredicates();

        int axis = step.getAxis();
        if (axis == Compiler.AXIS_SELF) {
            return doPredicate(
                context,
                parent,
                steps,
                currentStep,
                predicates,
                0);
        }

        Expression predicate = predicates[0];

        // Optimize for a single predicate to avoid building a list
        // and to allow the direct access to the index'th element
        // in the case of a simple subscript predecate
        // It is a very common use case, so it deserves individual
        // attention
        if (predicates.length == 1) {
            NodeIterator it = getNodeIterator(context, parent, step);
            NodePointer pointer = null;
            if (it != null) {
                if (predicate instanceof NameAttributeTest) { // [@name = key]
                    String key = keyFromPredicate(context, predicate);
                    for (int i = 1; it.setPosition(i); i++) {
                        NodePointer ptr = it.getNodePointer();
                        if (isNameAttributeEqual(ptr, key)) {
                            pointer = ptr;
                            break;
                        }
                    }
                }
                else {
                    int index = indexFromPredicate(context, predicate);
                    if (it.setPosition(index + 1)) {
                        pointer = it.getNodePointer();
                    }
                }
            }
            if (pointer != null) {
                return doStep(context, pointer, steps, currentStep + 1);
            }
        }
        else {
            NodeIterator it = getNodeIterator(context, parent, step);
            if (it != null) {
                List list = new ArrayList();
                for (int i = 1; it.setPosition(i); i++) {
                    list.add(it.getNodePointer());
                }
                NodePointer pointer =
                    doPredicatesStandard(
                        context,
                        list,
                        steps,
                        currentStep,
                        predicates,
                        0);
                if (pointer != null) {
                    return pointer;
                }
            }
        }
        return createNullPointer(context, parent, steps, currentStep);
    }

    /**
     * Evaluates predicates and proceeds with the subsequent steps
     * of the path.
     * @param context evaluation context
     * @param parent parent pointer
     * @param steps path steps
     * @param currentStep step number
     * @param predicates predicate expressions
     * @param currentPredicate int predicate number
     * @return NodePointer
     */
    private static NodePointer doPredicate(
                EvalContext context, NodePointer parent,
                Step[] steps, int currentStep,
                Expression[] predicates, int currentPredicate) {
        if (currentPredicate == predicates.length) {
            return doStep(context, parent, steps, currentStep + 1);
        }

        Expression predicate = predicates[currentPredicate];
        if (predicate instanceof NameAttributeTest) { // [@name = key1]
            return doPredicateName(
                context,
                parent,
                steps,
                currentStep,
                predicates,
                currentPredicate);
        }
        // else [index]
        return doPredicateIndex(
            context,
            parent,
            steps,
            currentStep,
            predicates,
            currentPredicate);
    }

    /**
     * Execute a NameAttributeTest predicate
     * @param context evaluation context
     * @param parent parent pointer
     * @param steps path steps
     * @param currentStep int step number
     * @param predicates predicates
     * @param currentPredicate int predicate number
     * @return NodePointer
     */
    private static NodePointer doPredicateName(
            EvalContext context, NodePointer parent,
            Step[] steps, int currentStep,
            Expression[] predicates, int currentPredicate) {
        Expression predicate = predicates[currentPredicate];
        String key = keyFromPredicate(context, predicate);
        NodePointer child = valuePointer(parent);
        if (child instanceof PropertyOwnerPointer) {
            PropertyPointer pointer =
                ((PropertyOwnerPointer) child).getPropertyPointer();
            pointer.setPropertyName(key);
            if (pointer.isActual()) {
                return doPredicate(
                    context,
                    pointer,
                    steps,
                    currentStep,
                    predicates,
                    currentPredicate + 1);
            }
        }
        else if (child.isCollection()) {
            // For each node in the collection, perform the following:
            // if the node is a property owner, apply this predicate to it;
            // if the node is a collection, apply this predicate to each elem.;
            // if the node is not a prop owner or a collection,
            //  see if it has the attribute "name" with the right value,
            //  if so - proceed to the next predicate
            NodePointer bestMatch = null;
            int bestQuality = 0;
            child = (NodePointer) child.clone();
            int count = child.getLength();
            for (int i = 0; i < count; i++) {
                child.setIndex(i);
                NodePointer valuePointer = valuePointer(child);
                NodePointer pointer;
                if ((valuePointer instanceof PropertyOwnerPointer)
                    || valuePointer.isCollection()) {
                    pointer =
                        doPredicateName(
                            context,
                            valuePointer,
                            steps,
                            currentStep,
                            predicates,
                            currentPredicate);
                }
                else if (isNameAttributeEqual(valuePointer, key)) {
                    pointer =
                        doPredicate(
                            context,
                            valuePointer,
                            steps,
                            currentStep,
                            predicates,
                            currentPredicate + 1);
                }
                else {
                    pointer = null;
                }
                if (pointer != null) {
                    int quality = computeQuality(pointer);
                    if (quality == PERFECT_MATCH) {
                        return pointer;
                    }
                    if (quality > bestQuality) {
                        bestMatch = (NodePointer) pointer.clone();
                        bestQuality = quality;
                    }
                }
            }
            if (bestMatch != null) {
                return bestMatch;
            }
        }
        else {
            // If the node is a standard InfoSet node (e.g. DOM Node),
            // employ doPredicates_standard, which will iterate through
            // the node's children and apply all predicates
            NodePointer found =
                doPredicatesStandard(
                    context,
                    Collections.singletonList(child),
                    steps,
                    currentStep,
                    predicates,
                    currentPredicate);
            if (found != null) {
                return found;
            }
        }
        // If nothing worked - return a null pointer
        return createNullPointerForPredicates(
            context,
            child,
            steps,
            currentStep,
            predicates,
            currentPredicate);
    }

    /**
     * Called exclusively for standard InfoSet nodes, e.g. DOM nodes
     * to evaluate predicate sequences like [@name=...][@name=...][index].
     * @param context evaluation context
     * @param parents List of parent pointers
     * @param steps path steps
     * @param currentStep step number
     * @param predicates predicates
     * @param currentPredicate int predicate number
     * @return NodePointer
     */
    private static NodePointer doPredicatesStandard(
                EvalContext context, List parents,
                Step[] steps, int currentStep,
                Expression[] predicates, int currentPredicate) {
        if (parents.size() == 0) {
            return null;
        }

        // If all predicates have been processed, take the first
        // element from the list of results and proceed to the
        // remaining steps with that element.
        if (currentPredicate == predicates.length) {
            NodePointer pointer = (NodePointer) parents.get(0);
            return doStep(context, pointer, steps, currentStep + 1);
        }

        Expression predicate = predicates[currentPredicate];
        if (predicate instanceof NameAttributeTest) {
            String key = keyFromPredicate(context, predicate);
            List newList = new ArrayList();
            for (int i = 0; i < parents.size(); i++) {
                NodePointer pointer = (NodePointer) parents.get(i);
                if (isNameAttributeEqual(pointer, key)) {
                    newList.add(pointer);
                }
            }
            if (newList.size() == 0) {
                return null;
            }
            return doPredicatesStandard(
                context,
                newList,
                steps,
                currentStep,
                predicates,
                currentPredicate + 1);
        }
        else {
            // For a subscript, simply take the corresponding
            // element from the list of results and
            // proceed to the remaining predicates with that element
            int index = indexFromPredicate(context, predicate);
            if (index < 0 || index >= parents.size()) {
                return null;
            }
            NodePointer ptr = (NodePointer) parents.get(index);
            return doPredicate(
                context,
                ptr,
                steps,
                currentStep,
                predicates,
                currentPredicate + 1);
        }
    }

    /**
     * Evaluate a subscript predicate: see if the node is a collection and
     * if the index is inside the collection.
     * @param context evaluation context
     * @param parent parent pointer
     * @param steps path steps
     * @param currentStep step number
     * @param predicates predicates
     * @param currentPredicate int predicate number
     * @return NodePointer
     */
    private static NodePointer doPredicateIndex(
            EvalContext context, NodePointer parent,
            Step[] steps, int currentStep,
            Expression[] predicates, int currentPredicate) {
        Expression predicate = predicates[currentPredicate];
        int index = indexFromPredicate(context, predicate);
        NodePointer pointer = parent;
        if (isCollectionElement(pointer, index)) {
            pointer = (NodePointer) pointer.clone();
            pointer.setIndex(index);
            return doPredicate(
                context,
                pointer,
                steps,
                currentStep,
                predicates,
                currentPredicate + 1);
        }
        return createNullPointerForPredicates(
            context,
            parent,
            steps,
            currentStep,
            predicates,
            currentPredicate);
    }

    /**
     * Extract an integer from a subscript predicate. The returned index
     * starts with 0, even though the subscript starts with 1.
     * @param context evaluation context
     * @param predicate to evaluate
     * @return calculated index
     */
    private static int indexFromPredicate(
        EvalContext context,
        Expression predicate) {
        Object value = predicate.computeValue(context);
        if (value instanceof EvalContext) {
            value = ((EvalContext) value).getSingleNodePointer();
        }
        if (value instanceof NodePointer) {
            value = ((NodePointer) value).getValue();
        }
        if (value == null) {
            throw new JXPathException("Predicate value is null: " + predicate);
        }

        if (value instanceof Number) {
            final double round = 0.5;
            return (int) (InfoSetUtil.doubleValue(value) + round) - 1;
        }
        return InfoSetUtil.booleanValue(value) ? 0 : -1;
    }

    /**
     * Extracts the string value of the expression from a predicate like
     * [@name=expression].
     * @param context evaluation context
     * @param predicate predicate to evaluate
     * @return String key extracted
     */
    private static String keyFromPredicate(EvalContext context,
                Expression predicate) {
        Expression expr =
            ((NameAttributeTest) predicate).getNameTestExpression();
        return InfoSetUtil.stringValue(expr.computeValue(context));
    }

    /**
     * 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.
     * @param pointer input pointer
     * @return int match quality code
     */
    private static int computeQuality(NodePointer pointer) {
        int quality = PERFECT_MATCH;
        while (pointer != null && !pointer.isActual()) {
            quality--;
            pointer = pointer.getImmediateParentPointer();
        }
        return quality;
    }

    /**
     * Returns true if the pointer has an attribute called "name" and
     * its value is equal to the supplied string.
     * @param pointer input pointer
     * @param name name to check
     * @return boolean
     */
    private static boolean isNameAttributeEqual(
        NodePointer pointer,
        String name) {
        NodeIterator it = pointer.attributeIterator(QNAME_NAME);
        return it != null
            && it.setPosition(1)
            && name.equals(it.getNodePointer().getValue());
    }

    /**
     * Returns true if the pointer is a collection and the index is
     * withing the bounds of the collection.
     * @param pointer input pointer
     * @param index to check
     * @return boolean
     */
    private static boolean isCollectionElement(
        NodePointer pointer,
        int index) {
        return pointer.isActual()
            && (index == 0
                || (pointer.isCollection()
                    && index >= 0
                    && index < pointer.getLength()));
    }

    /**
     * For an intermediate pointer (e.g. PropertyPointer, ContainerPointer)
     * returns a pointer for the contained value.
     * @param pointer input pointer
     * @return NodePointer
     */
    private static NodePointer valuePointer(NodePointer pointer) {
        return pointer == null ? null : pointer.getValuePointer();
    }

    /**
     * Creates a "null pointer" that
     * a) represents the requested path and
     * b) can be used for creation of missing nodes in the path.
     * @param context evaluation context
     * @param parent parent pointer
     * @param steps path steps
     * @param currentStep step number
     * @return NodePointer
     */
    public static NodePointer createNullPointer(
            EvalContext context, NodePointer parent, Step[] steps,
            int currentStep) {
        if (currentStep == steps.length) {
            return parent;
        }

        parent = valuePointer(parent);

        Step step = steps[currentStep];

        int axis = step.getAxis();
        if (axis == Compiler.AXIS_CHILD || axis == Compiler.AXIS_ATTRIBUTE) {
            NullPropertyPointer pointer = new NullPropertyPointer(parent);
            QName name = ((NodeNameTest) step.getNodeTest()).getNodeName();
            pointer.setPropertyName(name.toString());
            pointer.setAttribute(axis == Compiler.AXIS_ATTRIBUTE);
            parent = pointer;
        }
        // else { it is self::node() }

        Expression[] predicates = step.getPredicates();
        return createNullPointerForPredicates(
            context,
            parent,
            steps,
            currentStep,
            predicates,
            0);
    }

    /**
     * Creates a "null pointer" that starts with predicates.
     * @param context evaluation context
     * @param parent parent pointer
     * @param steps path steps
     * @param currentStep step number
     * @param predicates predicates
     * @param currentPredicate int predicate number
     * @return NodePointer
     */
    private static NodePointer createNullPointerForPredicates(
            EvalContext context, NodePointer parent,
            Step[] steps, int currentStep,
            Expression[] predicates, int currentPredicate) {
        for (int i = currentPredicate; i < predicates.length; i++) {
            Expression predicate = predicates[i];
            if (predicate instanceof NameAttributeTest) {
                String key = keyFromPredicate(context, predicate);
                parent = valuePointer(parent);
                NullPropertyPointer pointer = new NullPropertyPointer(parent);
                pointer.setNameAttributeValue(key);
                parent = pointer;
            }
            else {
                int index = indexFromPredicate(context, predicate);
                if (parent instanceof NullPropertyPointer) {
                    parent.setIndex(index);
                }
                else {
                    parent = new NullElementPointer(parent, index);
                }
            }
        }
        // Proceed with the remaining steps
        return createNullPointer(
                    context, parent, steps, currentStep + 1);
    }

    /**
     * Get a NodeIterator.
     * @param context evaluation context
     * @param pointer owning pointer
     * @param step triggering step
     * @return NodeIterator
     */
    private static NodeIterator getNodeIterator(
        EvalContext context,
        NodePointer pointer,
        Step step) {
        if (step.getAxis() == Compiler.AXIS_CHILD) {
            NodeTest nodeTest = step.getNodeTest();
            QName qname = ((NodeNameTest) nodeTest).getNodeName();
            String prefix = qname.getPrefix();
            if (prefix != null) {
                String namespaceURI = context.getJXPathContext()
                        .getNamespaceURI(prefix);
                nodeTest = new NodeNameTest(qname, namespaceURI);
            }
            return pointer.childIterator(nodeTest, false, null);
        }
        // else Compiler.AXIS_ATTRIBUTE
        if (!(step.getNodeTest() instanceof NodeNameTest)) {
            throw new UnsupportedOperationException(
                "Not supported node test for attributes: "
                    + step.getNodeTest());
        }
        return pointer.attributeIterator(
            ((NodeNameTest) step.getNodeTest()).getNodeName());
    }

    /**
     * Learn whether <code>name</code> is a lang attribute.
     * @param name to compare
     * @return boolean
     */
    private static boolean isLangAttribute(QName name) {
        return name.getPrefix() != null
            && name.getPrefix().equals("xml")
            && name.getName().equals("lang");
    }
}
