package org.exist.xquery.functions.array;
import org.exist.dom.QName;
import org.exist.xquery.*;
import org.exist.xquery.value.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Functions on arrays {@link http://www.w3.org/TR/xpath-functions-31/#array-functions}.
*
* @author Wolf
*/
public class ArrayFunction extends BasicFunction {
private enum Fn {
SIZE("size"),
GET("get"),
APPEND("append"),
HEAD("head"),
TAIL("tail"),
SUBARRAY("subarray"),
REMOVE("remove"),
INSERT_BEFORE("insert-before"),
REVERSE("reverse"),
JOIN("join"),
FOR_EACH("for-each"),
FILTER("filter"),
FOLD_LEFT("fold-left"),
FOLD_RIGHT("fold-right"),
FOR_EACH_PAIR("for-each-pair"),
FLATTEN("flatten");
final static Map<String, Fn> fnMap = new HashMap<>();
static {
for (Fn fn: Fn.values()) {
fnMap.put(fn.fname, fn);
}
}
static Fn get(String name) {
return fnMap.get(name);
}
private final String fname;
Fn(String name) {
this.fname = name;
}
}
public static final FunctionSignature[] signatures = {
new FunctionSignature(
new QName(Fn.SIZE.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Returns the number of members in the supplied array.",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array")
},
new FunctionReturnSequenceType(Type.INTEGER, Cardinality.EXACTLY_ONE, "The number of members in the supplied array")
),
new FunctionSignature(
new QName(Fn.GET.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Gets the value at the specified position in the supplied array (counting from 1). This is the same " +
"as calling $array($index).",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array"),
new FunctionParameterSequenceType("index", Type.INTEGER, Cardinality.EXACTLY_ONE, "The index")
},
new FunctionReturnSequenceType(Type.INTEGER, Cardinality.ZERO_OR_MORE, "The value at $index")
),
new FunctionSignature(
new QName(Fn.APPEND.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Returns an array containing all the members of the supplied array, plus one additional" +
"member at the end.",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array"),
new FunctionParameterSequenceType("appendage", Type.ITEM, Cardinality.ZERO_OR_MORE, "The items to append")
},
new FunctionReturnSequenceType(Type.ARRAY, Cardinality.ZERO_OR_MORE, "A copy of $array with the new member attached")
),
new FunctionSignature(
new QName(Fn.HEAD.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Returns the first member of an array, i.e. $array(1)",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array")
},
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE, "The first member of the array")
),
new FunctionSignature(
new QName(Fn.TAIL.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Returns an array containing all members except the first from a supplied array.",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array")
},
new FunctionReturnSequenceType(Type.ARRAY, Cardinality.EXACTLY_ONE, "A new array containing all members except the first")
),
new FunctionSignature(
new QName(Fn.SUBARRAY.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Gets an array containing all members from a supplied array starting at a supplied position, up to the end of the array",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array"),
new FunctionParameterSequenceType("start", Type.INTEGER, Cardinality.EXACTLY_ONE, "The start index")
},
new FunctionReturnSequenceType(Type.ARRAY, Cardinality.EXACTLY_ONE, "A new array containing all members from $start")
),
new FunctionSignature(
new QName(Fn.SUBARRAY.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Gets an array containing all members from a supplied array starting at a supplied position, up to a specified length.",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array"),
new FunctionParameterSequenceType("start", Type.INTEGER, Cardinality.EXACTLY_ONE, "The start index"),
new FunctionParameterSequenceType("length", Type.INTEGER, Cardinality.EXACTLY_ONE, "Length of the subarray")
},
new FunctionReturnSequenceType(Type.ARRAY, Cardinality.EXACTLY_ONE, "A new array containing all members from $start up to the specified length")
),
new FunctionSignature(
new QName(Fn.REMOVE.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Returns an array containing all members from $array except the member whose position is $position.",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array"),
new FunctionParameterSequenceType("position", Type.INTEGER, Cardinality.EXACTLY_ONE, "Position of the member to remove")
},
new FunctionReturnSequenceType(Type.ARRAY, Cardinality.EXACTLY_ONE, "A new array containing all members except the one at $position")
),
new FunctionSignature(
new QName(Fn.INSERT_BEFORE.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Returns an array containing all the members of the supplied array, with one additional member at a specified position.",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array"),
new FunctionParameterSequenceType("position", Type.INTEGER, Cardinality.EXACTLY_ONE, "Position at which the new member is inserted"),
new FunctionParameterSequenceType("member", Type.ITEM, Cardinality.ZERO_OR_MORE, "The member to insert")
},
new FunctionReturnSequenceType(Type.ARRAY, Cardinality.EXACTLY_ONE, "A new array containing all members plus the new member")
),
new FunctionSignature(
new QName(Fn.REVERSE.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Returns an array containing all the members of the supplied array, but in reverse order.",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array")
},
new FunctionReturnSequenceType(Type.ARRAY, Cardinality.EXACTLY_ONE, "The array in reverse order")
),
new FunctionSignature(
new QName(Fn.JOIN.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Concatenates the contents of several arrays into a single array",
new SequenceType[] {
new FunctionParameterSequenceType("arrays", Type.ARRAY, Cardinality.ZERO_OR_MORE, "The arrays to join")
},
new FunctionReturnSequenceType(Type.ARRAY, Cardinality.EXACTLY_ONE, "The resulting array")
),
new FunctionSignature(
new QName(Fn.FOR_EACH.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Returns an array whose size is the same as array:size($array), in which each member is computed by applying " +
"$function to the corresponding member of $array.",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array to process"),
new FunctionParameterSequenceType("function", Type.FUNCTION_REFERENCE, Cardinality.EXACTLY_ONE, "The function called on each member of the array")
},
new FunctionReturnSequenceType(Type.ARRAY, Cardinality.EXACTLY_ONE, "The resulting array")
),
new FunctionSignature(
new QName(Fn.FILTER.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Returns an array containing those members of the $array for which $function returns true.",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array to process"),
new FunctionParameterSequenceType("function", Type.FUNCTION_REFERENCE, Cardinality.EXACTLY_ONE, "The function called on each member of the array")
},
new FunctionReturnSequenceType(Type.ARRAY, Cardinality.EXACTLY_ONE, "The resulting array")
),
new FunctionSignature(
new QName(Fn.FOLD_LEFT.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Evaluates the supplied function cumulatively on successive values of the supplied array.",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array to process"),
new FunctionParameterSequenceType("zero", Type.ITEM, Cardinality.ZERO_OR_MORE, "Start value"),
new FunctionParameterSequenceType("function", Type.FUNCTION_REFERENCE, Cardinality.EXACTLY_ONE, "The function to call")
},
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE, "The result of the cumulative function call")
),
new FunctionSignature(
new QName(Fn.FOLD_RIGHT.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Evaluates the supplied function cumulatively on successive values of the supplied array.",
new SequenceType[] {
new FunctionParameterSequenceType("array", Type.ARRAY, Cardinality.EXACTLY_ONE, "The array to process"),
new FunctionParameterSequenceType("zero", Type.ITEM, Cardinality.ZERO_OR_MORE, "Start value"),
new FunctionParameterSequenceType("function", Type.FUNCTION_REFERENCE, Cardinality.EXACTLY_ONE, "The function to call")
},
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE, "The result of the cumulative function call")
),
new FunctionSignature(
new QName(Fn.FOR_EACH_PAIR.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Returns an array obtained by evaluating the supplied function once for each pair of members at the same position in the two " +
"supplied arrays.",
new SequenceType[] {
new FunctionParameterSequenceType("array1", Type.ARRAY, Cardinality.EXACTLY_ONE, "The first array to process"),
new FunctionParameterSequenceType("array1", Type.ARRAY, Cardinality.EXACTLY_ONE, "The second array to process"),
new FunctionParameterSequenceType("function", Type.FUNCTION_REFERENCE, Cardinality.EXACTLY_ONE, "The function to call for each pair")
},
new FunctionReturnSequenceType(Type.ARRAY, Cardinality.EXACTLY_ONE, "The resulting array")
),
new FunctionSignature(
new QName(Fn.FLATTEN.fname, ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Replaces an array appearing in a supplied sequence with the members of the array, recursively.",
new SequenceType[] {
new FunctionParameterSequenceType("input", Type.ITEM, Cardinality.ZERO_OR_MORE, "The sequence to flatten")
},
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE, "The resulting sequence")
)
};
private AnalyzeContextInfo cachedContextInfo;
public ArrayFunction(XQueryContext context, FunctionSignature signature) {
super(context, signature);
}
@Override
public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
cachedContextInfo = new AnalyzeContextInfo(contextInfo);
super.analyze(contextInfo);
}
@Override
public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {
if (context.getXQueryVersion() < 31) {
throw new XPathException(this, ErrorCodes.EXXQDY0004, "arrays are only available in XQuery 3.1, but version declaration states " +
context.getXQueryVersion());
}
final Fn called = Fn.get(getSignature().getName().getLocalPart());
switch (called) {
case JOIN:
final List<ArrayType> arrays = new ArrayList<ArrayType>(args[0].getItemCount());
for (SequenceIterator i = args[0].iterate(); i.hasNext(); ) {
arrays.add((ArrayType) i.nextItem());
}
return ArrayType.join(context, arrays);
case FLATTEN:
final ValueSequence result = new ValueSequence(args[0].getItemCount());
ArrayType.flatten(args[0], result);
return result;
default:
final ArrayType array = (ArrayType) args[0].itemAt(0);
switch (called) {
case SIZE:
return new IntegerValue(array.getSize());
case GET:
final IntegerValue index = (IntegerValue) args[1].itemAt(0);
return array.get(index.getInt() - 1);
case APPEND:
return array.append(args[1]);
case HEAD:
if (array.getSize() == 0) {
throw new XPathException(this, ErrorCodes.FOAY0001, "Array is empty");
}
return array.get(0);
case TAIL:
if (array.getSize() == 0) {
throw new XPathException(this, ErrorCodes.FOAY0001, "Array is empty");
}
return array.tail();
case SUBARRAY:
final int start = ((IntegerValue) args[1].itemAt(0)).getInt();
int end = array.getSize();
if (getArgumentCount() == 3) {
final int length = ((IntegerValue) args[2].itemAt(0)).getInt();
if (start + length > array.getSize() + 1) {
throw new XPathException(this, ErrorCodes.FOAY0001, "Array index out of bounds: " + (start + length - 1));
}
if (length < 0) {
throw new XPathException(this, ErrorCodes.FOAY0002, "Specified length < 0");
}
end = start + length - 1;
}
if (start < 1) {
throw new XPathException(this, ErrorCodes.FOAY0001, "Start index into array is < 1");
}
return array.subarray(start - 1, end);
case REMOVE:
final int rpos = ((IntegerValue) args[1].itemAt(0)).getInt();
if (rpos < 1 || rpos > array.getSize()) {
throw new XPathException(this, ErrorCodes.FOAY0001, "Index of item to remove (" + rpos + ") is out of bounds");
}
return array.remove(rpos - 1);
case INSERT_BEFORE:
final int ipos = ((IntegerValue) args[1].itemAt(0)).getInt();
if (ipos < 1 || ipos > array.getSize() + 1) {
throw new XPathException(this, ErrorCodes.FOAY0001, "Index of item to insert (" + ipos + ") is out of bounds");
}
return array.insertBefore(ipos - 1, args[2]);
case REVERSE:
return array.reverse();
case FOR_EACH:
return array.forEach(getFunction(args[1]));
case FILTER:
return array.filter(getFunction(args[1]));
case FOLD_LEFT:
return array.foldLeft(getFunction(args[2]), args[1]);
case FOLD_RIGHT:
return array.foldRight(getFunction(args[2]), args[1]);
case FOR_EACH_PAIR:
return array.forEachPair((ArrayType) args[1].itemAt(0), getFunction(args[2]));
}
}
throw new XPathException(this, "Unknown function: " + getName());
}
private FunctionReference getFunction(Sequence arg) throws XPathException {
final FunctionReference ref = (FunctionReference) arg.itemAt(0);
ref.analyze(cachedContextInfo);
return ref;
}
}