<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.instance.DataInstance;
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.xpath.XPathArityException;
import org.javarosa.xpath.parser.XPathSyntaxException;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;

/**
 * Base class for xpath function expressions.
 * Dispatches to runtime function overrides when they exist.
 */
public abstract class XPathFuncExpr extends XPathExpression {
    protected String name;
    public XPathExpression[] args;
    protected Object[] evaluatedArgs;
    protected int expectedArgCount;
    private boolean evaluateArgsFirst;

    @SuppressWarnings("unused")
    public XPathFuncExpr() {
        // for deserialization
    }

    public XPathFuncExpr(String name, XPathExpression[] args,
                         int expectedArgCount, boolean evaluateArgsFirst)
            throws XPathSyntaxException {
        this.name = name;
        this.args = args;
        this.expectedArgCount = expectedArgCount;
        this.evaluateArgsFirst = evaluateArgsFirst;

        validateArgCount();
    }

    @Override
    public final Object evalRaw(DataInstance model, EvaluationContext evalContext) {
        evaluateArguments(model, evalContext);

        IFunctionHandler handler = evalContext.getFunctionHandlers().get(name);
        if (handler != null) {
            return XPathCustomRuntimeFunc.evalCustomFunction(handler, evaluatedArgs, evalContext);
        } else {
            return evalBody(model, evalContext);
        }
    }

    private void evaluateArguments(DataInstance model, EvaluationContext evalContext) {
        if (evaluateArgsFirst) {
            if (evaluatedArgs == null) {
                evaluatedArgs = new Object[args.length];
            }
            for (int i = 0; i < args.length; i++) {
                evaluatedArgs[i] = args[i].eval(model, evalContext);
            }
        }
    }

    protected abstract Object evalBody(DataInstance model, EvaluationContext evalContext);

    //protected abstract String docs();

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

        sb.append("{func-expr:");
        sb.append(name);<commit_msg>Always build evalArgs array to prevent NPE in matchPrototypes<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.instance.DataInstance;
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.xpath.XPathArityException;
import org.javarosa.xpath.parser.XPathSyntaxException;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;

/**
 * Base class for xpath function expressions.
 * Dispatches to runtime function overrides when they exist.
 */
public abstract class XPathFuncExpr extends XPathExpression {
    protected String name;
    public XPathExpression[] args;
    protected Object[] evaluatedArgs;
    protected int expectedArgCount;
    private boolean evaluateArgsFirst;

    @SuppressWarnings("unused")
    public XPathFuncExpr() {
        // for deserialization
    }

    public XPathFuncExpr(String name, XPathExpression[] args,
                         int expectedArgCount, boolean evaluateArgsFirst)
            throws XPathSyntaxException {
        this.name = name;
        this.args = args;
        this.expectedArgCount = expectedArgCount;
        this.evaluateArgsFirst = evaluateArgsFirst;

        validateArgCount();
    }

    @Override
    public final Object evalRaw(DataInstance model, EvaluationContext evalContext) {
        evaluateArguments(model, evalContext);

        IFunctionHandler handler = evalContext.getFunctionHandlers().get(name);
        if (handler != null) {
            return XPathCustomRuntimeFunc.evalCustomFunction(handler, evaluatedArgs, evalContext);
        } else {
            return evalBody(model, evalContext);
        }
    }

    private void evaluateArguments(DataInstance model, EvaluationContext evalContext) {
        if (evaluatedArgs == null) {
            evaluatedArgs = new Object[args.length];
        }
        if (evaluateArgsFirst) {
            for (int i = 0; i < args.length; i++) {
                evaluatedArgs[i] = args[i].eval(model, evalContext);
            }
        }
    }

    protected abstract Object evalBody(DataInstance model, EvaluationContext evalContext);

    //protected abstract String docs();

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

        sb.append("{func-expr:");
        sb.append(name);
{'fraction_non_alphanumeric': 0.0871475649945075, 'fraction_numerical': 0.0003661662394727206, 'mean_word_length': 3.3049645390070923, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, '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': '828589', 'n_tokens_mistral': 1464, 'n_tokens_neox': 1434, 'n_words': 325}