package org.basex.query.expr;
import java.io.IOException;
import org.basex.io.serial.Serializer;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.item.Item;
import org.basex.query.item.SeqType;
import org.basex.query.item.SeqType.Occ;
import org.basex.query.item.Value;
import org.basex.query.iter.Iter;
import org.basex.query.iter.ItemCache;
import org.basex.query.path.AxisPath;
import org.basex.query.util.Var;
import org.basex.util.Array;
import org.basex.util.InputInfo;
/**
* Filter expression.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public class Filter extends Preds {
/** Expression. */
Expr root;
/**
* Constructor.
* @param ii input info
* @param r expression
* @param p predicates
*/
public Filter(final InputInfo ii, final Expr r, final Expr... p) {
super(ii, p);
root = r;
}
@Override
public final Expr comp(final QueryContext ctx) throws QueryException {
root = checkUp(root, ctx).comp(ctx);
// return empty root
if(root.isEmpty()) return optPre(null, ctx);
// convert filters without numeric predicates to axis paths
if(root instanceof AxisPath && !super.uses(Use.POS))
return ((AxisPath) root).copy().addPreds(preds).comp(ctx);
// optimize filter expressions
final Expr e = super.comp(ctx);
if(e != this) return e;
// no predicates.. return root; otherwise, do some advanced compilations
return preds.length == 0 ? root : comp2(ctx);
}
/**
* Compiles the filter expression, excluding the root node.
* @param ctx query context
* @return compiled expression
*/
public final Expr comp2(final QueryContext ctx) {
// evaluate return type
final SeqType t = root.type();
// determine number of results and type
final long s = root.size();
if(s != -1) {
if(pos != null) {
size = Math.max(0, s + 1 - pos.min) - Math.max(0, s - pos.max);
} else if(last) {
size = s > 0 ? 1 : 0;
}
// no results will remain: return empty sequence
if(size == 0) return optPre(null, ctx);
type = SeqType.get(t.type, size);
} else {
type = SeqType.get(t.type, t.zeroOrOne() ? Occ.ZO : Occ.ZM);
}
// no numeric predicates.. use simple iterator
if(!super.uses(Use.POS)) return new IterFilter(this);
// one single position() or last() function specified:
if(preds.length == 1 && (last || pos != null)) {
// return single value
if(root.isValue() && t.one() && (last || pos.min == 1 && pos.max == 1)) {
return optPre(root, ctx);
}
}
// check if offset will not be deterministic; e.g.:
// (1 to 10)[xs:int(math:random() * 10)]
final boolean off = preds.length == 1 && preds[0].type().isNum() &&
!preds[0].uses(Use.CTX) && !preds[0].uses(Use.NDT);
// iterator for simple numeric predicate
return off || useIterator() ? new IterPosFilter(this, off) : this;
}
@Override
public Iter iter(final QueryContext ctx) throws QueryException {
final Iter iter = ctx.iter(root);
final Value cv = ctx.value;
final long cs = ctx.size;
final long cp = ctx.pos;
try {
// cache results to support last() function
final ItemCache ic = new ItemCache();
for(Item i; (i = iter.next()) != null;) ic.add(i);
// evaluate predicates
for(final Expr p : preds) {
final long is = ic.size();
ctx.size = is;
ctx.pos = 1;
int c = 0;
for(int s = 0; s < is; ++s) {
ctx.value = ic.get(s);
if(p.test(ctx, input) != null) ic.set(ic.get(s), c++);
ctx.pos++;
}
ic.size(c);
}
return ic;
} finally {
ctx.value = cv;
ctx.size = cs;
ctx.pos = cp;
}
}
/**
* Adds a predicate to the filter.
* @param p predicate to be added
* @return self reference
*/
public final Filter addPred(final Expr p) {
preds = Array.add(preds, p);
return this;
}
@Override
public final boolean uses(final Use u) {
return root.uses(u) || u != Use.CTX && super.uses(u);
}
@Override
public final int count(final Var v) {
return root.count(v) + super.count(v);
}
@Override
public final boolean removable(final Var v) {
return root.removable(v) && super.removable(v);
}
@Override
public final Expr remove(final Var v) {
root = root.remove(v);
return super.remove(v);
}
@Override
public final void plan(final Serializer ser) throws IOException {
ser.openElement(this);
root.plan(ser);
super.plan(ser);
ser.closeElement();
}
@Override
public final String toString() {
return "(" + root + ')' + super.toString();
}
}