package org.exist.xquery.functions.array;
import com.github.krukow.clj_lang.*;
import org.exist.dom.QName;
import org.exist.xquery.*;
import org.exist.xquery.value.*;
import java.util.ArrayList;
import java.util.List;
/**
* Implements the array type (XQuery 3.1). An array is also a function. This class thus extends
* {@link FunctionReference} to allow the item to be called in a dynamic function
* call.
*
* Based on immutable, persistent vectors. Operations like append, head, tail, reverse should be fast.
* Remove and insert-before require copying the array.
*
* @author Wolf
*/
public class ArrayType extends FunctionReference implements Lookup.LookupSupport {
// the signature of the function which is evaluated if the map is called as a function item
private static final FunctionSignature ACCESSOR =
new FunctionSignature(
new QName("get", ArrayModule.NAMESPACE_URI, ArrayModule.PREFIX),
"Internal accessor function for arrays.",
new SequenceType[]{
new FunctionParameterSequenceType("n", Type.POSITIVE_INTEGER, Cardinality.EXACTLY_ONE, "the position of the item to retrieve from the array")
},
new SequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE));
private InternalFunctionCall accessorFunc;
private IPersistentVector<Sequence> vector;
private XQueryContext context;
public ArrayType(XQueryContext context, List<Sequence> items) {
this(context);
vector = PersistentVector.create(items);
}
public ArrayType(XQueryContext context, Sequence items) throws XPathException {
this(context);
List<Sequence> itemList = new ArrayList<Sequence>(items.getItemCount());
for (SequenceIterator i = items.iterate(); i.hasNext(); ) {
itemList.add(i.nextItem().toSequence());
}
vector = PersistentVector.create(itemList);
}
public ArrayType(XQueryContext context, IPersistentVector<Sequence> vector) {
this(context);
this.vector = vector;
}
private ArrayType(XQueryContext context) {
super(null);
this.context = context;
final Function fn = new AccessorFunc(context);
this.accessorFunc = new InternalFunctionCall(fn);
}
public Sequence get(int n) {
return vector.nth(n);
}
@Override
public Sequence get(AtomicValue key) throws XPathException {
if (!Type.subTypeOf(key.getType(), Type.INTEGER)) {
throw new XPathException(ErrorCodes.XPTY0004, "position argument for array lookup must be a positive integer");
}
final int pos = ((IntegerValue)key).getInt();
if (pos <= 0 || pos > getSize()) {
throw new XPathException(ErrorCodes.XPTY0004, "position argument for array lookup must be > 0 and < array:size");
}
return get(pos - 1);
}
@Override
public Sequence keys() throws XPathException {
return asSequence();
}
public Sequence tail() throws XPathException {
if (vector.length() == 2) {
final Sequence tail = vector.nth(1);
return new ArrayType(context, tail);
}
return new ArrayType(context, RT.subvec(vector, 1, vector.length()));
}
public ArrayType subarray(int start, int end) throws XPathException {
return new ArrayType(context, RT.subvec(vector, start, end));
}
public ArrayType remove(int position) throws XPathException {
ITransientCollection<Sequence> ret = PersistentVector.emptyVector().asTransient();
for(int i = 0; i < vector.length(); i++) {
if (position != i) {
ret = ret.conj(vector.nth(i));
}
}
return new ArrayType(context, (IPersistentVector<Sequence>)ret.persistent());
}
public ArrayType insertBefore(int position, Sequence member) throws XPathException {
ITransientCollection<Sequence> ret = PersistentVector.emptyVector().asTransient();
for(int i = 0; i < vector.length(); i++) {
if (position == i) {
ret = ret.conj(member);
}
ret = ret.conj(vector.nth(i));
}
if (position == vector.length()) {
ret = ret.conj(member);
}
return new ArrayType(context, (IPersistentVector<Sequence>)ret.persistent());
}
public static ArrayType join(XQueryContext context, List<ArrayType> arrays) {
final ITransientCollection<Sequence> ret = PersistentVector.emptyVector().asTransient();
for (ArrayType type: arrays) {
for (ISeq<Sequence> seq = type.vector.seq(); seq != null; seq = seq.next()) {
ret.conj(seq.first());
}
}
return new ArrayType(context, (IPersistentVector<Sequence>)ret.persistent());
}
/**
* Add member. Modifies the array! Don't use unless you're constructing a new array.
*
* @param seq
*/
public void add(Sequence seq) {
vector = vector.cons(seq);
}
/**
* Return a new array with a member appended.
*
* @param seq
* @return
*/
public ArrayType append(Sequence seq) {
return new ArrayType(this.context, vector.cons(seq));
}
public ArrayType reverse() {
final IPersistentVector<Sequence> rvec = PersistentVector.create(vector.rseq());
return new ArrayType(this.context, rvec);
}
public Sequence asSequence() throws XPathException {
ValueSequence result = new ValueSequence(vector.length());
for (int i = 0; i < vector.length(); i++) {
result.addAll(vector.nth(i));
}
return result;
}
public Sequence[] toArray() {
final Sequence[] array = new Sequence[vector.length()];
return (Sequence[]) RT.seqToPassedArray(vector.seq(), array);
}
public int getSize() {
return vector.length();
}
@Override
public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
accessorFunc.analyze(contextInfo);
}
@Override
public Sequence eval(Sequence contextSequence) throws XPathException {
return accessorFunc.eval(contextSequence);
}
@Override
public void setArguments(List<Expression> arguments) throws XPathException {
accessorFunc.setArguments(arguments);
}
@Override
public void resetState(boolean postOptimization) {
accessorFunc.resetState(postOptimization);
}
@Override
public int getType() {
return Type.ARRAY;
}
@Override
public int getItemType() {
return Type.ARRAY;
}
@Override
public AtomicValue atomize() throws XPathException {
if (vector.length() == 0) {
return null;
} else if (vector.length() > 1) {
throw new XPathException(ErrorCodes.XPTY0004, "Expected single atomic value but found array with length " + vector.length());
}
final Sequence member = vector.nth(0);
if (member.hasMany()) {
throw new XPathException(ErrorCodes.XPTY0004, "Expected single atomic value but found sequence of length " + member.getItemCount());
}
return member.itemAt(0).atomize();
}
public ArrayType forEach(FunctionReference ref) throws XPathException {
final ITransientCollection<Sequence> ret = PersistentVector.emptyVector().asTransient();
final Sequence fargs[] = new Sequence[1];
for (ISeq<Sequence> seq = vector.seq(); seq != null; seq = seq.next()) {
fargs[0] = seq.first();
ret.conj(ref.evalFunction(null, null, fargs));
}
return new ArrayType(context, (IPersistentVector<Sequence>)ret.persistent());
}
public ArrayType forEachPair(ArrayType other, FunctionReference ref) throws XPathException {
final ITransientCollection<Sequence> ret = PersistentVector.emptyVector().asTransient();
for (ISeq<Sequence> i1 = vector.seq(), i2 = other.vector.seq(); i1 != null && i2 != null; i1 = i1.next(), i2 = i2.next()) {
ret.conj(ref.evalFunction(null, null, new Sequence[]{ i1.first(), i2.first() }));
}
return new ArrayType(context, (IPersistentVector<Sequence>)ret.persistent());
}
public ArrayType filter(FunctionReference ref) throws XPathException {
final ITransientCollection<Sequence> ret = PersistentVector.emptyVector().asTransient();
final Sequence fargs[] = new Sequence[1];
for (ISeq<Sequence> seq = vector.seq(); seq != null; seq = seq.next()) {
fargs[0] = seq.first();
final Sequence fret = ref.evalFunction(null, null, fargs);
if (fret.effectiveBooleanValue()) {
ret.conj(fargs[0]);
}
}
return new ArrayType(context, (IPersistentVector<Sequence>)ret.persistent());
}
public Sequence foldLeft(FunctionReference ref, Sequence zero) throws XPathException {
for (ISeq<Sequence> seq = vector.seq(); seq != null; seq = seq.next()) {
zero = ref.evalFunction(null, null, new Sequence[] { zero, seq.first() });
}
return zero;
}
public Sequence foldRight(FunctionReference ref, Sequence zero) throws XPathException {
ISeq<Sequence> seq = vector.seq();
return foldRight(ref, zero, seq);
}
private Sequence foldRight(FunctionReference ref, Sequence zero, ISeq<Sequence> seq) throws XPathException {
if (seq == null) {
return zero;
}
final Sequence head = seq.first();
final Sequence tailResult = foldRight(ref, zero, seq.next());
return ref.evalFunction(null, null, new Sequence[] { head, tailResult });
}
protected static Sequence flatten(Sequence input, ValueSequence result) throws XPathException {
for (SequenceIterator i = input.iterate(); i.hasNext(); ) {
final Item item = i.nextItem();
if (item.getType() == Type.ARRAY) {
final Sequence members = ((ArrayType)item).asSequence();
flatten(members, result);
} else {
result.add(item);
}
}
return result;
}
public static Sequence flatten(Item item) throws XPathException {
if (item.getType() == Type.ARRAY) {
final Sequence members = ((ArrayType)item).asSequence();
return flatten(members, new ValueSequence(members.getItemCount()));
}
return item.toSequence();
}
/**
* Flatten the given sequence by recursively replacing arrays with their member sequence.
*
* @param input
* @return
* @throws XPathException
*/
public static Sequence flatten(Sequence input) throws XPathException {
if (input.hasOne()) {
return flatten(input.itemAt(0));
}
boolean flatten = false;
final int itemType = input.getItemType();
if (itemType == Type.ARRAY) {
flatten = true;
} else if (itemType == Type.ITEM) {
// may contain arrays - check
for (SequenceIterator i = input.iterate(); i.hasNext(); ) {
if (i.nextItem().getType() == Type.ARRAY) {
flatten = true;
break;
}
}
}
return flatten ? flatten(input, new ValueSequence(input.getItemCount() * 2)) : input;
}
/**
* The accessor function which will be evaluated if the map is called
* as a function item.
*/
private class AccessorFunc extends BasicFunction {
public AccessorFunc(XQueryContext context) {
super(context, ACCESSOR);
}
public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {
final IntegerValue v = (IntegerValue) args[0].itemAt(0);
final int n = v.getInt();
if (n <= 0 || n > ArrayType.this.getSize()) {
throw new XPathException(this, ErrorCodes.FOAY0001, "Position " + n + " does not exist in this array. Length is " + ArrayType.this.getSize());
}
return ArrayType.this.get(n - 1);
}
}
}