<commit_before>package org.javarosa.xpath.expr;

import org.javarosa.core.model.condition.EvaluationContext;
import org.javarosa.core.model.condition.IFunctionHandler;
import org.javarosa.core.model.condition.pivot.UnpivotableExpressionException;
import org.javarosa.core.model.data.GeoPointData;
import org.javarosa.core.model.data.UncastData;
import org.javarosa.core.model.instance.DataInstance;
import org.javarosa.core.model.instance.TreeReference;
import org.javarosa.core.model.utils.DateUtils;
import org.javarosa.core.model.utils.GeoPointUtils;
import org.javarosa.core.util.CacheTable;
import org.javarosa.core.util.DataUtil;
import org.javarosa.core.util.MathUtils;
import org.javarosa.core.util.PropertyUtils;
import org.javarosa.core.util.externalizable.DeserializationException;
import org.javarosa.core.util.externalizable.ExtUtil;
import org.javarosa.core.util.externalizable.ExtWrapListPoly;
import org.javarosa.core.util.externalizable.PrototypeFactory;
import org.javarosa.xform.util.CalendarUtils;
import org.javarosa.xpath.IExprDataType;
import org.javarosa.xpath.XPathArityException;
import org.javarosa.xpath.XPathException;
import org.javarosa.xpath.XPathLazyNodeset;
import org.javarosa.xpath.XPathNodeset;
import org.javarosa.xpath.XPathTypeMismatchException;
import org.javarosa.xpath.XPathUnhandledException;
import org.javarosa.xpath.XPathUnsupportedException;
import org.javarosa.xpath.parser.XPathSyntaxException;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import me.regexp.RE;
import me.regexp.RESyntaxException;

/**
 * Representation of an xpath function expression.
 *
 * All of the built-in xpath functions are included here, as well as the xpath type conversion logic
 *
 * Evaluation of functions can delegate out to custom function handlers that must be registered at
 * runtime.
 *
 * @author <NAME>
 */
public class XPathFuncExpr extends XPathExpression {
    public XPathQName id;            //name of the function
    public XPathExpression[] args;    //argument list

    private static final CacheTable<String, Double> mDoubleParseCache = new CacheTable<>();

    public XPathFuncExpr() {
    } //for deserialization

    public XPathFuncExpr(XPathQName id, XPathExpression[] args) throws XPathSyntaxException {
        if ("if".equals(id.name) && args.length != 3) {
            throw new XPathSyntaxException("if() function requires 3 arguments but " + args.length + " are present.");
        } else if ("cond".equals(id.name)) {
            if (args.length < 3) {
                throw new XPathSyntaxException("cond() function requires at least 3 arguments. " + args.length + " arguments provided.");
            } else if (args.length % 2 != 1) {
                throw new XPathSyntaxException("cond() function requires an odd number of arguments. " + args.length + " arguments provided.");
            }
        }

        this.id = id;
        this.args = args;
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();

        sb.append("{func-expr:");
        sb.append(id.toString());
        sb.append(",{");
        for (int i = 0; i < args.length; i++) {
            sb.append(args[i].toString());
            if (i < args.length - 1)
                sb.append(",");
        }
        sb.append("}}");

        return sb.toString();
    }

    @Override
    public String toPrettyString() {
        StringBuffer sb = new StringBuffer();
        sb.append(id.toString() + "(");
        for (int i = 0; i < args.length; i++) {
            sb.append(args[i].toPrettyString());
            if (i < args.length - 1) {
                sb.append(", ");
            }
        }
        sb.append(")");
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof XPathFuncExpr) {
            XPathFuncExpr x = (XPathFuncExpr)o;

            //Shortcuts for very easily comprable values
            //We also only return "True" for methods we expect to return the same thing. This is not good
            //practice in Java, since o.equals(o) will return false. We should evaluate that differently.
            //Dec 8, 2011 - Added "uuid", since we should never assume one uuid equals another
            //May 6, 2013 - Added "random", since two calls asking for a random
            if (!id.equals(x.id) || args.length != x.args.length || id.toString().equals("uuid") || id.toString().equals("random")) {
                return false;
            }

            return ExtUtil.arrayEquals(args, x.args, false);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        int argsHash = 0;
        for (XPathExpression arg : args) {
            argsHash ^= arg.hashCode();
        }
        return id.hashCode() ^ argsHash;
    }

    @Override
    public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException {
        id = (XPathQName)ExtUtil.read(in, XPathQName.class);
        Vector v = (Vector)ExtUtil.read(in, new ExtWrapListPoly(), pf);

        args = new XPathExpression[v.size()];
        for (int i = 0; i < args.length; i++) {
            args[i] = (XPathExpression)v.elementAt(i);
        }
    }

    @Override
    public void writeExternal(DataOutputStream out) throws IOException {
        Vector<XPathExpression> v = new Vector<>();
        for (XPathExpression arg : args) {
            v.addElement(arg);
        }

        ExtUtil.write(out, id);
        ExtUtil.write(out, new ExtWrapListPoly(v));
    }

    /**
     * Evaluate the function call.
     *
     * First check if the function is a member of the built-in function suite. If not, then check
     * for any custom handlers registered to handler the function. If not, throw and exception.
     *
     * Both function name and appropriate arguments are taken into account when finding a suitable
     * handler. For built-in functions, the number of arguments must match; for custom functions,
     * the supplied arguments must match one of the function prototypes defined by the handler.
     */
    @Override
    public Object evalRaw(DataInstance model, EvaluationContext evalContext) {
        String name = id.toString();
        Object[] argVals = new Object[args.length];

        Hashtable funcHandlers = evalContext.getFunctionHandlers();

        //TODO: Func handlers should be able to declare the desire for short circuiting as well
        if (name.equals("if") && args.length == 3) {
            return ifThenElse(model, evalContext, args, argVals);
        } else if (name.equals("coalesce") && args.length > 0) {
            return coalesceEval(model, evalContext, args);
        } else if (name.equals("cond")) {
            return condEval(model, evalContext, args);
        }

        for (int i = 0; i < args.length; i++) {
            argVals[i] = args[i].eval(model, evalContext);
        }

        XPathArityException customFuncArityError = null;
        // check for custom handler, use this if it exists.
        try {
            IFunctionHandler handler = (IFunctionHandler)funcHandlers.get(name);
            if (handler != null) {
                return evalCustomFunction(handler, argVals, evalContext);
            }<commit_msg>Fix merge conflict<commit_after>package org.javarosa.xpath.expr;

import org.javarosa.core.model.condition.EvaluationContext;
import org.javarosa.core.model.condition.IFunctionHandler;
import org.javarosa.core.model.condition.pivot.UnpivotableExpressionException;
import org.javarosa.core.model.data.GeoPointData;
import org.javarosa.core.model.data.UncastData;
import org.javarosa.core.model.instance.DataInstance;
import org.javarosa.core.model.instance.TreeReference;
import org.javarosa.core.model.utils.DateUtils;
import org.javarosa.core.model.utils.GeoPointUtils;
import org.javarosa.core.util.CacheTable;
import org.javarosa.core.util.DataUtil;
import org.javarosa.core.util.MathUtils;
import org.javarosa.core.util.PropertyUtils;
import org.javarosa.core.util.externalizable.DeserializationException;
import org.javarosa.core.util.externalizable.ExtUtil;
import org.javarosa.core.util.externalizable.ExtWrapListPoly;
import org.javarosa.core.util.externalizable.PrototypeFactory;
import org.javarosa.xform.util.CalendarUtils;
import org.javarosa.xpath.IExprDataType;
import org.javarosa.xpath.XPathArityException;
import org.javarosa.xpath.XPathException;
import org.javarosa.xpath.XPathLazyNodeset;
import org.javarosa.xpath.XPathNodeset;
import org.javarosa.xpath.XPathTypeMismatchException;
import org.javarosa.xpath.XPathUnhandledException;
import org.javarosa.xpath.XPathUnsupportedException;
import org.javarosa.xpath.parser.XPathSyntaxException;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import me.regexp.RE;
import me.regexp.RESyntaxException;

/**
 * Representation of an xpath function expression.
 *
 * All of the built-in xpath functions are included here, as well as the xpath type conversion logic
 *
 * Evaluation of functions can delegate out to custom function handlers that must be registered at
 * runtime.
 *
 * @author <NAME>
 */
public class XPathFuncExpr extends XPathExpression {
    public XPathQName id;            //name of the function
    public XPathExpression[] args;    //argument list

    private static final CacheTable<String, Double> mDoubleParseCache = new CacheTable<>();

    public XPathFuncExpr() {
    } //for deserialization

    public XPathFuncExpr(XPathQName id, XPathExpression[] args) throws XPathSyntaxException {
        if ("if".equals(id.name) && args.length != 3) {
            throw new XPathSyntaxException("if() function requires 3 arguments but " + args.length + " are present.");
        } else if ("cond".equals(id.name)) {
            if (args.length < 3) {
                throw new XPathSyntaxException("cond() function requires at least 3 arguments. " + args.length + " arguments provided.");
            } else if (args.length % 2 != 1) {
                throw new XPathSyntaxException("cond() function requires an odd number of arguments. " + args.length + " arguments provided.");
            }
        }

        this.id = id;
        this.args = args;
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();

        sb.append("{func-expr:");
        sb.append(id.toString());
        sb.append(",{");
        for (int i = 0; i < args.length; i++) {
            sb.append(args[i].toString());
            if (i < args.length - 1)
                sb.append(",");
        }
        sb.append("}}");

        return sb.toString();
    }

    @Override
    public String toPrettyString() {
        StringBuffer sb = new StringBuffer();
        sb.append(id.toString() + "(");
        for (int i = 0; i < args.length; i++) {
            sb.append(args[i].toPrettyString());
            if (i < args.length - 1) {
                sb.append(", ");
            }
        }
        sb.append(")");
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof XPathFuncExpr) {
            XPathFuncExpr x = (XPathFuncExpr)o;

            //Shortcuts for very easily comprable values
            //We also only return "True" for methods we expect to return the same thing. This is not good
            //practice in Java, since o.equals(o) will return false. We should evaluate that differently.
            //Dec 8, 2011 - Added "uuid", since we should never assume one uuid equals another
            //May 6, 2013 - Added "random", since two calls asking for a random
            if (!id.equals(x.id) || args.length != x.args.length || id.toString().equals("uuid") || id.toString().equals("random")) {
                return false;
            }

            return ExtUtil.arrayEquals(args, x.args, false);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        int argsHash = 0;
        for (XPathExpression arg : args) {
            argsHash ^= arg.hashCode();
        }
        return id.hashCode() ^ argsHash;
    }

    @Override
    public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException {
        id = (XPathQName)ExtUtil.read(in, XPathQName.class);
        Vector v = (Vector)ExtUtil.read(in, new ExtWrapListPoly(), pf);

        args = new XPathExpression[v.size()];
        for (int i = 0; i < args.length; i++) {
            args[i] = (XPathExpression)v.elementAt(i);
        }
    }

    @Override
    public void writeExternal(DataOutputStream out) throws IOException {
        Vector<XPathExpression> v = new Vector<>();
        for (XPathExpression arg : args) {
            v.addElement(arg);
        }

        ExtUtil.write(out, id);
        ExtUtil.write(out, new ExtWrapListPoly(v));
    }

    /**
     * Evaluate the function call.
     *
     * First check if the function is a member of the built-in function suite. If not, then check
     * for any custom handlers registered to handler the function. If not, throw and exception.
     *
     * Both function name and appropriate arguments are taken into account when finding a suitable
     * handler. For built-in functions, the number of arguments must match; for custom functions,
     * the supplied arguments must match one of the function prototypes defined by the handler.
     */
    @Override
    public Object evalRaw(DataInstance model, EvaluationContext evalContext) {
        String name = id.toString();
        Object[] argVals = new Object[args.length];

        Hashtable funcHandlers = evalContext.getFunctionHandlers();

        //TODO: Func handlers should be able to declare the desire for short circuiting as well
        if (name.equals("if") && args.length == 3) {
            return ifThenElse(model, evalContext, args);
        } else if (name.equals("coalesce") && args.length > 0) {
            return coalesceEval(model, evalContext, args);
        } else if (name.equals("cond")) {
            return condEval(model, evalContext, args);
        }

        for (int i = 0; i < args.length; i++) {
            argVals[i] = args[i].eval(model, evalContext);
        }

        XPathArityException customFuncArityError = null;
        // check for custom handler, use this if it exists.
        try {
            IFunctionHandler handler = (IFunctionHandler)funcHandlers.get(name);
            if (handler != null) {
                return evalCustomFunction(handler, argVals, evalContext);
            }
{'fraction_non_alphanumeric': 0.10781942765014108, 'fraction_numerical': 0.0033588606744592233, 'mean_word_length': 2.695878848063555, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7249579', 'n_tokens_mistral': 4317, 'n_tokens_neox': 4195, 'n_words': 1165}