////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.xpath;

import net.sf.saxon.Configuration;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.instruct.Executable;
import net.sf.saxon.expr.instruct.SlotManager;
import net.sf.saxon.expr.sort.AtomicComparer;
import net.sf.saxon.expr.sort.SortKeyDefinition;
import net.sf.saxon.expr.sort.SortKeyEvaluator;
import net.sf.saxon.expr.sort.SortedIterator;
import net.sf.saxon.functions.NumberFn;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.NodeTest;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.wrapper.VirtualNode;
import net.sf.saxon.value.*;
import org.xml.sax.InputSource;

import javax.xml.namespace.QName;
import javax.xml.transform.sax.SAXSource;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import java.util.List;

/**
  * <p>The JAXP XPathExpression interface represents a compiled XPath expression that can be repeatedly
  * evaluated. This class is Saxon's implementation of that interface.</p>
  *
  * <p>The class also includes some methods retained from Saxon's original XPath API. When these methods
  * are used, the object contains the context node and other state, so it is not thread-safe.</p>
 *
  * @author Michael H. Kay
  */


public class XPathExpressionImpl implements XPathExpression, SortKeyEvaluator {

    private Configuration config;
    private Executable executable;
    private Expression expression;
    private Expression atomizer;
    /*@Nullable*/ private NodeInfo contextNode;
    private SlotManager stackFrameMap;
    /*@Nullable*/ private XPathExpressionImpl sortKey = null;

    /**
     * The constructor is protected, to ensure that instances can only be
     * created using the createExpression() method of XPathEvaluator
     * @param exp the compiled expression
     * @param exec the executable
    */

    protected XPathExpressionImpl(Expression exp, /*@NotNull*/ Executable exec) {
        expression = exp;
        executable = exec;
        config = exec.getConfiguration();
    }

    /**
     * Define the number of slots needed for local variables within the expression.
     * This method is for internal use only.
     * @param map description of the stack frame
     */

    protected void setStackFrameMap(SlotManager map) {
        stackFrameMap = map;
    }

    /**
     * Get the stack frame map. This holds information about the allocation of slots to variables.
     * This is needed by applications using low-level interfaces for evaluating the expression
     * @return a description of the stack frame
     */

    public SlotManager getStackFrameMap() {
        return stackFrameMap;
    }

    /**
     * Get the Configuration under which this XPath expression was compiled
     * @return the Saxon configuration
     */

    public Configuration getConfiguration() {
        return config;
    }

    /**
     * Define the sort order for the results of the expression. If this method is called, then
     * the list returned by a subsequent call on the evaluate() method will first be sorted.
     * @param sortKey an XPathExpression, which will be applied to each item in the sequence;
     * the result of this expression determines the ordering of the list returned by the evaluate()
     * method. The sortKey can be null, to clear a previous sort key. Note that the expression is
     * not automatically atomized; if it selects nodes, these should be explicitly converted to
     * atomic values by calling the string() or data() functions.
     * @deprecated since 9.0. This method is not present in the JAXP interface. The recommended
     * way to get a sorted result is to use XQuery instead of XPath.
    */

    public void setSortKey(XPathExpressionImpl sortKey) {
        this.sortKey = sortKey;
    }

    /**
     * Set the context node for evaluating the expression. If this method is not called,
     * the context node will be the root of the document to which the prepared expression is
     * bound.
     * @param node the context node
     * @deprecated since 9.0. Using this method is not thread-safe. Use a method instead
     * such as {@link #evaluate(Object, QName)} that allows the context node to be specified
     * as a parameter to the call.
    */

    public void setContextNode(/*@Nullable*/ NodeInfo node) {
        if (node==null) {
            throw new NullPointerException("Context node cannot be null");
        }
        if (node.getConfiguration() != config) {
            throw new IllegalArgumentException("Supplied node uses the wrong Configuration");
        }
        contextNode = node;
    }

    /**
     * Protected, undeprecated version of setContextNode() for use by deprecated paths within the package
     * (exists to avoid deprecation warnings when compiling Saxon)
     * @param node the context node
     */

    protected void privatelySetContextNode(/*@Nullable*/ NodeInfo node) {
        if (node==null) {
            throw new NullPointerException("Context node cannot be null");
        }
        if (node.getConfiguration() != config) {
            throw new IllegalArgumentException("Supplied node uses the wrong Configuration");
        }
        contextNode = node;
    }


    /**
     * Execute a prepared XPath expression, returning the results as a List. The context
     * node must have been set previously using {@link #setContextNode(net.sf.saxon.om.NodeInfo)}.
     * @return The results of the expression, as a List. The List represents the sequence
     * of items returned by the expression. Each item in the list will either be an instance
     * of net.sf.saxon.om.NodeInfo, representing a node, or a Java object representing an atomic value.
     * For the types of Java object that may be returned, see {@link #evaluate(Object, javax.xml.namespace.QName)}
     * with the second argument set to NODESET.
     * @deprecated since 9.0. This method is not present in the JAXP interface. Either use
     * the JAXP methods such as {@link #evaluate(Object, QName)}, or use the Saxon XPath
     * API instead of JAXP.
    */

    /*@NotNull*/ public List evaluate() throws XPathException {
        XPathContextMajor context = new XPathContextMajor(contextNode, executable);
        context.openStackFrame(stackFrameMap);
        SequenceIterator iter = expression.iterate(context);
        SequenceExtent extent = new SequenceExtent(iter);
        return (List)PJConverter.ToCollection.INSTANCE.convert(extent, List.class, context);
    }

    /**
     * Execute a prepared XPath expression, returning the first item in the result.
     * This is useful where it is known that the expression will only return
     * a singleton value (for example, a single node, or a boolean). The context node
     * must be set previously using {@link #setContextNode(net.sf.saxon.om.NodeInfo)}.
     * @return The first item in the sequence returned by the expression. If the expression
     * returns an empty sequence, this method returns null. Otherwise, it returns the first
     * item in the result sequence, represented as a Java object using the same mapping as for
     * the evaluate() method
     * @deprecated since 9.0. This method is not present in the JAXP interface. Either use
     * the JAXP methods such as {@link #evaluate(Object, QName)}, or use the Saxon XPath
     * API instead of JAXP.
     */

    /*@Nullable*/ public Object evaluateSingle() throws XPathException {
        XPathContextMajor context = new XPathContextMajor(contextNode, executable);
        context.openStackFrame(stackFrameMap);
        SequenceIterator iterator = expression.iterate(context);
        Item item = iterator.next();
        if (item == null) {
            return null;
        } else {
            return SequenceTool.convertToJava(item);
        }
    }

    /**
     * Get a raw iterator over the results of the expression. This returns results without
     * any conversion of the returned items to "native" Java classes. This method is intended
     * for use by applications that need to process the results of the expression using
     * internal Saxon interfaces.
     * @param contextItem the context item for evaluating the expression
     * @return an iterator over the results of the expression, with no conversion of returned items
     * @since 9.0
    */

    /*@Nullable*/ public SequenceIterator rawIterator(Item contextItem) throws XPathException {
        XPathContextMajor context = new XPathContextMajor(contextItem, executable);
        return rawIterator(context);
    }

    /*@Nullable*/ private SequenceIterator rawIterator(/*@NotNull*/ XPathContextMajor context) throws XPathException {
        context.openStackFrame(stackFrameMap);
        SequenceIterator iterator = expression.iterate(context);
        if (sortKey != null) {
            Expression key = sortKey.expression;
            if (key.getItemType(config.getTypeHierarchy()) instanceof NodeTest) {
                sortKey.expression = Atomizer.makeAtomizer(key);
            }

            SortKeyDefinition sk = new SortKeyDefinition();
            sk.setSortKey(sortKey.expression, true);
            AtomicComparer comp = sk.makeComparator(context);
            AtomicComparer[] comps = {comp};

            iterator = new SortedIterator(context, iterator, this, comps, true);
            ((SortedIterator)iterator).setHostLanguage(Configuration.XPATH);
        }
        return iterator;
    }


    /**
     * JAXP 1.3 evaluate() method
     * @param node The context node. This must use a representation of nodes that this implementation understands.
     * This may be a Saxon NodeInfo, or a node in one of the external object models supported, for example
     * DOM, DOM4J, JDOM, or XOM, provided the support module for that object model is loaded.
     *
     * <p><b>Contrary to the interface specification, Saxon does not supply an empty
     * document when the value is null. This is because Saxon supports multiple object models,
     * and it's unclear what kind of document node would be appropriate. Instead, Saxon uses
     * the node supplied to the {@link #setContextNode} method if available, and if none
     * is available, executes the XPath expression with the context item undefined.</p></p>
     * <p><b>Saxon does not allow a NodeList to be supplied for this parameter. It's not clear
     * what this would be intended to mean.</b></p>
     * @param qName Indicates the type of result required. This must be one of the constants defined in
     * the JAXP {@link XPathConstants} class.
     * Saxon will attempt to convert the actual result of the expression to the required type using the
     * XPath 1.0 conversion rules.
     * @return the result of the evaluation, as a Java object of the appropriate type. Saxon interprets the
     * rules as follows:
     * <table>
     * <thead><tr><td>QName</td><td>Return Value</td></thead>
     * <tbody>
     *   <tr><td valign="top">BOOLEAN</td>
     *       <td>The effective boolean value of the actual result,
     *           as a Java Boolean object</td></tr>
     *   <tr><td valign="top">STRING</td>
     *       <td>The result of applying the string() function to the actual result,
     *           as a Java String object</td></tr>
     *   <tr><td valign="top">NUMBER</td>
     *       <td>The result of applying the number() function to the actual result,
     *           as a Java Double object</td></tr>
     *   <tr><td valign="top">NODE</td>
     *       <td>A single node, in the native data model supplied as input. If the
     *           expression returns more than one node, the first is returned. If
     *           the expression returns an empty sequence, null is returned. If the
     *           expression returns an atomic value, or if the first item in the
     *           result sequence is an atomic value, an exception is thrown.</td></tr>
     *   <tr><td valign="top">NODESET</td>
     *       <td>This is interpreted as allowing any sequence, of nodes or atomic values.
     *           If the first argument is a wrapper around a DOM Node, then the result is
     *           returned as a DOM NodeList, and an exception is then thrown if the result sequence
     *           contains a value that is not a DOM Node. In all other cases
     *           the result is returned as a Java List object, unless it is empty, in which
     *           case null is returned. The contents of the list may be node objects (in the
     *           native data model supplied as input), or Java objects representing the XPath
     *           atomic values in the actual result: String for an xs:string, Double for a xs:double,
     *           Long for an xs:integer, and so on. (For safety, cast the values to a type
     *           such as xs:string within the XPath expression). </td></tr></table>
     *
     * @throws XPathExpressionException if evaluation of the expression fails or if the
     * result cannot be converted to the requested type.
     */
    /*@Nullable*/ public Object evaluate(/*@Nullable*/ Object node, /*@NotNull*/ QName qName) throws XPathExpressionException {
        NodeInfo contextNode = this.contextNode;
        if (node != null) {
            if (node instanceof SingletonItem) {
                node = ((SingletonItem)node).asItem();
            }
            if (node instanceof NodeInfo) {
                if (!((NodeInfo)node).getConfiguration().isCompatible(config)) {
                    throw new XPathExpressionException(
                                "Supplied node must be built using the same or a compatible Configuration");
                }
                if (node instanceof DocumentInfo && ((DocumentInfo)node).isTyped() && !executable.isSchemaAware()) {
                    throw new XPathExpressionException(
                            "The expression was compiled to handled untyped data, but the input is typed");
                }
                contextNode = ((NodeInfo)node);
            } else {
                JPConverter converter = JPConverter.allocate(node.getClass(), config);
                Sequence val;
                try {
                    val = converter.convert(node, new EarlyEvaluationContext(config, null));
                } catch (XPathException e) {
                    throw new XPathExpressionException(
                            "Failure converting a node of class " + node.getClass().getName() +
                            ": " + e.getMessage());
                }
                if (val instanceof NodeInfo) {
                    if (!((NodeInfo)val).getConfiguration().isCompatible(config)) {
                        throw new XPathExpressionException(
                                    "Supplied node must be built using the same or a compatible Configuration");
                    }
                    if (node instanceof DocumentInfo && ((DocumentInfo)node).isTyped() && !executable.isSchemaAware()) {
                        throw new XPathExpressionException(
                                "The expression was compiled to handled untyped data, but the input is typed");
                    }
                    contextNode = (NodeInfo)val;
                } else {
                    throw new XPathExpressionException(
                            "Cannot locate an object model implementation for nodes of class "
                            + node.getClass().getName());
                }
            }
        }
        XPathContextMajor context = new XPathContextMajor(contextNode, executable);
        context.openStackFrame(stackFrameMap);
        try {
            if (qName.equals(XPathConstants.BOOLEAN)) {
                return Boolean.valueOf(expression.effectiveBooleanValue(context));
            } else if (qName.equals(XPathConstants.STRING)) {
                SequenceIterator iter = expression.iterate(context);

                Item first = iter.next();
                if (first == null) {
                    return "";
                }
                return first.getStringValue();

            } else if (qName.equals(XPathConstants.NUMBER)) {
                if (atomizer == null) {
                    atomizer = Atomizer.makeAtomizer(expression);
                }
                SequenceIterator iter = atomizer.iterate(context);

                Item first = iter.next();
                if (first == null) {
                    return new Double(Double.NaN);
                }
                if (first instanceof NumericValue) {
                    return new Double(((NumericValue)first).getDoubleValue());
                } else {
                    DoubleValue v = NumberFn.convert((AtomicValue)first, getConfiguration());
                    return new Double(v.getDoubleValue());
                }

            } else if (qName.equals(XPathConstants.NODE)) {
                SequenceIterator iter = expression.iterate(context);
                Item first = iter.next();
                if (first instanceof VirtualNode) {
                    return ((VirtualNode)first).getRealNode();
                }
                if (first == null || first instanceof NodeInfo) {
                    return first;
                }
                throw new XPathExpressionException("Expression result is not a node");
            } else if (qName.equals(XPathConstants.NODESET)) {
                //SequenceIterator iter = expression.iterate(context);
                SequenceIterator iter = rawIterator(context);
                SequenceExtent extent = new SequenceExtent(iter);
                PJConverter converter = PJConverter.allocateNodeListCreator(config, node);
                return converter.convert(extent, Object.class, context);
            } else {
                throw new IllegalArgumentException("qName: Unknown type for expected result");
            }
        } catch (XPathException e) {
            throw new XPathExpressionException(e);
        }
    }

    /**
     * Evaluate the expression to return a string value
     * @param node the initial context node. This must be either an instance of NodeInfo or a node
     * recognized by a known external object model.
     * <p><b>Contrary to the interface specification, Saxon does not supply an empty
     * document when the value is null. This is because Saxon supports multiple object models,
     * and it's unclear what kind of document node would be appropriate. Instead, Saxon uses
     * the node supplied to the {@link #setContextNode} method if available, and if none
     * is available, executes the XPath expression with the context item undefined.</p></p>
     * @return the results of the expression, converted to a String
     * @throws XPathExpressionException if evaluation fails
     */

    /*@NotNull*/ public String evaluate(Object node) throws XPathExpressionException {
        return (String)evaluate(node, XPathConstants.STRING);
    }

    /**
     * Evaluate the XPath expression against an input source to obtain a result of a specified type
     * @param inputSource The input source document against which the expression is evaluated.
     * (Note that there is no caching. This will be parsed, and the parsed result will be discarded.)
     * If the supplied value is null then (contrary to the JAXP specifications), the XPath expression
     * is evaluated with the context item undefined.
     * @param qName The type required, identified by a constant in {@link XPathConstants}
     * @return the result of the evaluation, as a Java object of the appropriate type:
     * see {@link #evaluate(Object, javax.xml.namespace.QName)}
     * @throws XPathExpressionException
     */
    /*@Nullable*/ public Object evaluate(/*@Nullable*/ InputSource inputSource, /*@Nullable*/ QName qName) throws XPathExpressionException {
        if (qName == null) {
            throw new NullPointerException("qName");
        }
        try {
            NodeInfo doc = null;
            if (inputSource != null) {
                doc = config.buildDocument(new SAXSource(inputSource));
            }
            return evaluate(doc, qName);
        } catch (XPathException e) {
            throw new XPathExpressionException(e);
        }
    }

    /**
     * Evaluate the XPath expression against an input source to obtain a string result
     * @param inputSource The input source document against which the expression is evaluated.
     * (Note that there is no caching. This will be parsed, and the parsed result will be discarded.)
     * @return the result of the evaluation, converted to a String
     * @throws XPathExpressionException in the event of an XPath dynamic error
     * @throws NullPointerException If  <code>inputSource</code> is <code>null</code>.
     */

    /*@NotNull*/ public String evaluate(/*@Nullable*/ InputSource inputSource) throws XPathExpressionException {
        if (inputSource == null) {
            throw new NullPointerException("inputSource");
        }
        try {
            NodeInfo doc = config.buildDocument(new SAXSource(inputSource));
            return (String)evaluate(doc, XPathConstants.STRING);
        } catch (XPathException e) {
            throw new XPathExpressionException(e);
        }
    }

    /**
     * Callback for evaluating the sort keys. For internal use only.
     */

    /*@Nullable*/ public AtomicValue evaluateSortKey(int n, XPathContext c) throws XPathException {
        return (AtomicValue)sortKey.getInternalExpression().evaluateItem(c);
    }


    /**
     * Low-level method to get the internal Saxon expression object. This exposes a wide range of
     * internal methods that may be needed by specialized applications, and allows greater control
     * over the dynamic context for evaluating the expression.
     * @return the underlying Saxon expression object.
     */

    public Expression getInternalExpression() {
        return expression;
    }

}

