package org.basex.query.func; import org.basex.data.Data; import org.basex.index.StatsType; import org.basex.index.path.PathNode; import org.basex.query.QueryContext; import org.basex.query.QueryException; import org.basex.query.expr.CmpV.Op; import org.basex.query.expr.Expr; import org.basex.query.item.ANode; import org.basex.query.item.Atm; import org.basex.query.item.DBNode; import org.basex.query.item.Empty; import org.basex.query.item.Int; import org.basex.query.item.Item; import org.basex.query.item.Seq; import org.basex.query.item.SeqType; import org.basex.query.item.SeqType.Occ; import org.basex.query.item.Type; import org.basex.query.item.Value; import org.basex.query.iter.AxisIter; import org.basex.query.iter.ItemCache; import org.basex.query.iter.Iter; import org.basex.query.iter.NodeCache; import org.basex.query.iter.ValueIter; import org.basex.query.path.AxisPath; import org.basex.query.util.ItemSet; import org.basex.util.Array; import org.basex.util.InputInfo; import org.basex.util.list.ObjList; /** * Sequence functions. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class FNSeq extends StandardFunc { /** * Constructor. * @param ii input info * @param f function definition * @param e arguments */ public FNSeq(final InputInfo ii, final Function f, final Expr... e) { super(ii, f, e); } @Override public Item item(final QueryContext ctx, final InputInfo ii) throws QueryException { switch(sig) { case HEAD: return head(ctx); default: return super.item(ctx, ii); } } @Override public Iter iter(final QueryContext ctx) throws QueryException { switch(sig) { case INDEX_OF: return indexOf(ctx); case DISTINCT_VALUES: return distinctValues(ctx); case INSERT_BEFORE: return insertBefore(ctx); case REVERSE: return reverse(ctx); case REMOVE: return remove(ctx); case SUBSEQUENCE: return subsequence(ctx); case TAIL: return tail(ctx); case OUTERMOST: return most(ctx, true); case INNERMOST: return most(ctx, false); default: return super.iter(ctx); } } /** * Returns the outermost/innermost nodes of a node sequence, i.e. a node is * only contained, if none of its ancestors/descendants are. * @param ctx query context * @param outer outermost flag * @return outermost/innermost nodes * @throws QueryException exception */ private Iter most(final QueryContext ctx, final boolean outer) throws QueryException { final Iter iter = expr[0].iter(ctx); final NodeCache nc = new NodeCache().random(); for(Item it; (it = iter.next()) != null;) nc.add(checkNode(it)); final int len = (int) nc.size(); // only go further if there are at least two nodes if(len < 2) return nc; // after this, the iterator is sorted and duplicate free if(nc.dbnodes()) { // nodes are sorted, so ancestors always come before their descendants // the first/last node is thus always included in the output final DBNode fst = (DBNode) nc.get(outer ? 0 : len - 1); final Data data = fst.data; final ANode[] nodes = nc.item.clone(); if(outer) { // skip the subtree of the last added node nc.size(0); final DBNode dummy = new DBNode(fst.data, 0); final NodeCache src = new NodeCache(nodes, len); for(int next = 0, p; next < len; next = p < 0 ? -p - 1 : p) { final DBNode nd = (DBNode) nodes[next]; dummy.pre = nd.pre + data.size(nd.pre, data.kind(nd.pre)); p = src.binarySearch(dummy, next + 1, len - next - 1); nc.add(nd); } } else { // skip ancestors of the last added node nc.item[0] = fst; nc.size(1); int before = fst.pre; for(int i = len - 1; i-- != 0;) { final DBNode nd = (DBNode) nodes[i]; if(nd.pre + data.size(nd.pre, data.kind(nd.pre)) <= before) { nc.add(nd); before = nd.pre; } } // nodes were added in reverse order, correct that Array.reverse(nc.item, 0, (int) nc.size()); } return nc; } // multiple documents and/or constructed fragments final NodeCache out = new NodeCache(new ANode[len], 0); OUTER: for(int i = 0; i < len; i++) { final ANode nd = nc.item[i]; final AxisIter ax = outer ? nd.ancestor() : nd.descendant(); for(ANode a; (a = ax.next()) != null;) if(nc.indexOf(a, false) != -1) continue OUTER; out.add(nc.item[i]); } return out; } @Override public Expr cmp(final QueryContext ctx) throws QueryException { // static typing: // index-of will create integers, insert-before might add new types if(sig == Function.INDEX_OF || sig == Function.INSERT_BEFORE) return this; // all other types will return existing types final Type t = expr[0].type().type; Occ o = Occ.ZM; // at most one returned item if(sig == Function.SUBSEQUENCE && expr[0].type().one()) o = Occ.ZO; // head will return at most one item else if(sig == Function.HEAD) o = Occ.ZO; type = SeqType.get(t, o); // pre-evaluate distinct values if(sig == Function.DISTINCT_VALUES) return cmpDist(ctx); return this; } /** * Pre-evaluates distinct-values() function, utilizing database statistics. * @param ctx query context * @return original or optimized expression * @throws QueryException query exception */ private Expr cmpDist(final QueryContext ctx) throws QueryException { // can only be performed on axis paths if(!(expr[0] instanceof AxisPath)) return this; // try to get statistics for resulting nodes final ObjList<PathNode> nodes = ((AxisPath) expr[0]).nodes(ctx); if(nodes == null) return this; // loop through all nodes final ItemSet is = new ItemSet(); for(PathNode pn : nodes) { // retrieve text child if addressed node is an element if(pn.kind == Data.ELEM) { if(!pn.stats.isLeaf()) return this; for(final PathNode n : pn.ch) if(n.kind == Data.TEXT) pn = n; } // skip nodes others than texts and attributes if(pn.kind != Data.TEXT && pn.kind != Data.ATTR) return this; // check if distinct values are available if(pn.stats.type != StatsType.CATEGORY) return this; // if yes, add them to the item set for(final byte[] c : pn.stats.cats) is.index(input, new Atm(c)); } // return resulting sequence final ItemCache ic = new ItemCache(is.size()); for(final Item i : is) ic.add(i); return ic.value(); } /** * Returns the first item in a sequence. * @param ctx query context * @return first item * @throws QueryException query exception */ private Item head(final QueryContext ctx) throws QueryException { final Expr e = expr[0]; return e.type().zeroOrOne() ? e.item(ctx, input) : e.iter(ctx).next(); } /** * Returns all but the first item in a sequence. * @param ctx query context * @return iterator * @throws QueryException query exception */ private Iter tail(final QueryContext ctx) throws QueryException { final Expr e = expr[0]; if(e.type().zeroOrOne()) return Empty.ITER; final Iter ir = e.iter(ctx); if(ir.next() == null) return Empty.ITER; return new Iter() { @Override public Item next() throws QueryException { return ir.next(); } }; } /** * Returns the indexes of an item in a sequence. * @param ctx query context * @return position(s) of item * @throws QueryException query exception */ private Iter indexOf(final QueryContext ctx) throws QueryException { final Item it = checkItem(expr[1], ctx); if(expr.length == 3) checkColl(expr[2], ctx); return new Iter() { final Iter ir = expr[0].iter(ctx); int c; @Override public Item next() throws QueryException { while(true) { final Item i = ir.next(); if(i == null) return null; ++c; if(i.comparable(it) && Op.EQ.eval(input, i, it)) return Int.get(c); } } }; } /** * Returns all distinct values of a sequence. * @param ctx query context * @return distinct iterator * @throws QueryException query exception */ private Iter distinctValues(final QueryContext ctx) throws QueryException { if(expr.length == 2) checkColl(expr[1], ctx); return new Iter() { final ItemSet map = new ItemSet(); final Iter ir = expr[0].iter(ctx); @Override public Item next() throws QueryException { while(true) { Item i = ir.next(); if(i == null) return null; ctx.checkStop(); i = atom(i); if(map.index(input, i)) return i; } } }; } /** * Inserts items before the specified position. * @param ctx query context * @return iterator * @throws QueryException query exception */ private Iter insertBefore(final QueryContext ctx) throws QueryException { return new Iter() { final long pos = Math.max(1, checkItr(expr[1], ctx)); final Iter iter = expr[0].iter(ctx); final Iter ins = expr[2].iter(ctx); long p = pos; boolean last; @Override public Item next() throws QueryException { if(last) return p > 0 ? ins.next() : null; final boolean sub = p == 0 || --p == 0; final Item i = (sub ? ins : iter).next(); if(i != null) return i; if(sub) --p; else last = true; return next(); } }; } /** * Removes an item at a specified position in a sequence. * @param ctx query context * @return iterator without item * @throws QueryException query exception */ private Iter remove(final QueryContext ctx) throws QueryException { return new Iter() { final long pos = checkItr(expr[1], ctx); final Iter iter = expr[0].iter(ctx); long c; @Override public Item next() throws QueryException { return ++c != pos || iter.next() != null ? iter.next() : null; } }; } /** * Creates a subsequence out of a sequence, starting with start and * ending with end. * @param ctx query context * @return subsequence * @throws QueryException query exception */ private Iter subsequence(final QueryContext ctx) throws QueryException { final double ds = checkDbl(expr[1], ctx); if(Double.isNaN(ds)) return Empty.ITER; final long s = StrictMath.round(ds); long l = Long.MAX_VALUE; if(expr.length > 2) { final double dl = checkDbl(expr[2], ctx); if(Double.isNaN(dl)) return Empty.ITER; l = s + StrictMath.round(dl); } final long e = l; final Iter iter = ctx.iter(expr[0]); final long max = iter.size(); return max != -1 ? new Iter() { // directly access specified items final long m = Math.min(e, max + 1); long c = Math.max(1, s); @Override public Item next() throws QueryException { return c < m ? iter.get(c++ - 1) : null; } @Override public Item get(final long i) throws QueryException { return iter.get(c + i - 1); } @Override public long size() { return Math.max(0, m - c); } @Override public boolean reset() { c = Math.max(1, s); return true; } } : new Iter() { // run through all items long c; @Override public Item next() throws QueryException { while(true) { final Item i = iter.next(); if(i == null || ++c >= e) return null; if(c >= s) return i; } } }; } /** * Reverses a sequence. * @param ctx query context * @return iterator * @throws QueryException query exception */ private ValueIter reverse(final QueryContext ctx) throws QueryException { // has to be strictly evaluated final Value val = ctx.value(expr[0]); final ValueIter iter = val.iter(); // if only one item found: no reversion necessary return val.size() == 1 ? iter : new ValueIter() { final long s = iter.size(); long c = s; @Override public Item next() { return --c >= 0 ? iter.get(c) : null; } @Override public Item get(final long i) { return iter.get(s - i - 1); } @Override public long size() { return s; } @Override public boolean reset() { c = s; return true; } @Override public Value value() { final Item[] arr = new Item[(int) val.size()]; final int written = val.writeTo(arr, 0); Array.reverse(arr, 0, written); return Seq.get(arr, written); } }; } @Override public boolean uses(final Use u) { return u == Use.X30 && (sig == Function.HEAD || sig == Function.TAIL) || super.uses(u); } }