package org.basex.query.path;
import static org.basex.query.QueryText.*;
import static org.basex.query.path.Axis.*;
import java.io.IOException;
import org.basex.data.Data;
import org.basex.index.path.*;
import org.basex.io.serial.Serializer;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.expr.CAttr;
import org.basex.query.expr.CDoc;
import org.basex.query.expr.Context;
import org.basex.query.expr.Expr;
import org.basex.query.expr.ParseExpr;
import org.basex.query.expr.Root;
import org.basex.query.item.Empty;
import org.basex.query.item.NodeType;
import org.basex.query.item.QNm;
import org.basex.query.item.SeqType;
import org.basex.query.item.Value;
import org.basex.query.path.Test.Name;
import org.basex.query.util.Var;
import org.basex.util.InputInfo;
import org.basex.util.list.ObjList;
/**
* Path expression.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public abstract class Path extends ParseExpr {
/** Root expression. */
public Expr root;
/** Path steps. */
public Expr[] steps;
/**
* Constructor.
* @param ii input info
* @param r root expression; can be a {@code null} reference
* @param s axis steps
*/
Path(final InputInfo ii, final Expr r, final Expr[] s) {
super(ii);
root = r;
steps = s;
}
/**
* Returns a new path instance.
* @param ii input info
* @param r root expression; can be a {@code null} reference
* @param path path steps
* @return class instance
*/
public static Path get(final InputInfo ii, final Expr r,
final Expr... path) {
// check if all steps are axis steps
boolean axes = true;
for(final Expr p : path) axes &= p instanceof AxisStep;
return axes ?
new AxisPath(ii, r, path).finish(null) :
new MixedPath(ii, r, path);
}
@Override
public final Expr comp(final QueryContext ctx) throws QueryException {
if(root != null) {
root = checkUp(root, ctx).comp(ctx);
if(root instanceof Context) root = null;
}
final Value v = ctx.value;
try {
ctx.value = root(ctx);
return compPath(ctx);
} finally {
ctx.value = v;
}
}
/**
* Compiles the location path.
* @param ctx query context
* @return optimized expression
* @throws QueryException query exception
*/
protected abstract Expr compPath(final QueryContext ctx)
throws QueryException;
/**
* Returns the root of the current context or {@code null}.
* @param ctx query context
* @return root
*/
final Value root(final QueryContext ctx) {
final Value v = ctx != null ? ctx.value : null;
// no root specified: return context, if it does not reference a document
// as e.g. happens in //a(b|c)
if(root == null) return v == null || v.type != NodeType.DOC ? v : null;
// root is value: return root
if(root.isValue()) return (Value) root;
// no root reference, no context: return null
if(!(root instanceof Root) || v == null) return null;
// return context sequence or root of current context
return v.size() != 1 ? v : Root.root(v);
}
@Override
public final boolean uses(final Use use) {
// first step or root expression will be used as context
if(use == Use.CTX) return root == null || root.uses(use);
for(final Expr s : steps) if(s.uses(use)) return true;
return root != null && root.uses(use);
}
/**
* Optimizes descendant-or-self steps and static types.
* @param ctx query context
*/
void optSteps(final QueryContext ctx) {
boolean opt = false;
Expr[] st = steps;
for(int l = 1; l < st.length; ++l) {
if(!(st[l - 1] instanceof AxisStep &&
st[l] instanceof AxisStep)) continue;
final AxisStep prev = (AxisStep) st[l - 1];
final AxisStep curr = (AxisStep) st[l];
if(!prev.simple(DESCORSELF, false)) continue;
if(curr.axis == CHILD && !curr.uses(Use.POS)) {
// descendant-or-self::node()/child::X -> descendant::X
final int sl = st.length;
final Expr[] tmp = new Expr[sl - 1];
System.arraycopy(st, 0, tmp, 0, l - 1);
System.arraycopy(st, l, tmp, l - 1, sl - l);
st = tmp;
curr.axis = DESC;
opt = true;
} else if(curr.axis == ATTR && !curr.uses(Use.POS)) {
// descendant-or-self::node()/@X -> descendant-or-self::*/@X
prev.test = new NameTest(false);
opt = true;
}
}
if(opt) ctx.compInfo(OPTDESC);
// set atomic type for single attribute steps to speedup predicate tests
if(root == null && st.length == 1 && st[0] instanceof AxisStep) {
final AxisStep curr = (AxisStep) st[0];
if(curr.axis == ATTR && curr.test.test == Name.STD)
curr.type = SeqType.NOD_ZO;
}
steps = st;
}
/**
* Computes the number of results.
* @param ctx query context
* @return number of results
*/
long size(final QueryContext ctx) {
final Value rt = root(ctx);
final Data data = rt != null && rt.type == NodeType.DOC ? rt.data() : null;
if(data == null || !data.meta.pathindex || !data.meta.uptodate) return -1;
ObjList<PathNode> nodes = data.paths.root();
long m = 1;
for(int s = 0; s < steps.length; s++) {
final AxisStep curr = axisStep(s);
if(curr != null) {
nodes = curr.nodes(nodes, data);
if(nodes == null) return -1;
} else if(s + 1 == steps.length) {
m = steps[s].size();
} else {
// stop if a non-axis step is not placed last
return -1;
}
}
long sz = 0;
for(final PathNode pn : nodes) sz += pn.stats.count;
return sz * m;
}
/**
* Checks if the location path contains steps that will never yield results.
* @param stps step array
* @return empty step, or {@code null}
*/
AxisStep voidStep(final Expr[] stps) {
for(int l = 0; l < stps.length; ++l) {
final AxisStep s = axisStep(l);
if(s == null) continue;
final Axis sa = s.axis;
if(l == 0) {
if(root instanceof CAttr) {
if(sa == CHILD || sa == DESC) return s;
} else if(root instanceof Root || root instanceof Value &&
((Value) root).type == NodeType.DOC || root instanceof CDoc) {
if(sa != CHILD && sa != DESC && sa != DESCORSELF &&
(sa != SELF && sa != ANCORSELF ||
s.test != Test.NOD && s.test != Test.DOC)) return s;
}
} else {
final AxisStep ls = axisStep(l - 1);
if(ls == null) continue;
final Axis lsa = ls.axis;
if(sa == SELF || sa == DESCORSELF) {
// .../self:: / .../descendant-or-self::
if(s.test == Test.NOD) continue;
// @.../...
if(lsa == ATTR && s.test.type != NodeType.ATT) return s;
// text()/...
if(ls.test == Test.TXT && s.test != Test.TXT) return s;
if(sa == DESCORSELF) continue;
// .../self::
final QNm n1 = s.test.name;
final QNm n0 = ls.test.name;
if(n0 == null || n1 == null) continue;
// ...X/...Y
if(!n1.eq(n0)) return s;
} else if(sa == FOLLSIBL || sa == PRECSIBL) {
// .../following-sibling:: / .../preceding-sibling::
if(lsa == ATTR) return s;
} else if(sa == DESC || sa == CHILD || sa == ATTR) {
// .../descendant:: / .../child:: / .../attribute::
if(lsa == ATTR || ls.test == Test.TXT || ls.test == Test.COM ||
ls.test == Test.PI) return s;
} else if(sa == PARENT || sa == ANC) {
// .../parent:: / .../ancestor::
if(ls.test == Test.DOC) return s;
}
}
}
return null;
}
/**
* Converts descendant to child steps.
* @param ctx query context
* @param data data reference
* @return path
*/
Expr children(final QueryContext ctx, final Data data) {
// skip path check if no path index exists, or if it is out-of-dated
if(!data.meta.pathindex || !data.meta.uptodate ||
data.nspaces.globalNS() == null) return this;
Path path = this;
for(int s = 0; s < steps.length; ++s) {
// don't allow predicates in preceding location steps
final AxisStep prev = s > 0 ? axisStep(s - 1) : null;
if(prev != null && prev.preds.length != 0) break;
// ignore axes other than descendant, or numeric predicates
final AxisStep curr = axisStep(s);
if(curr == null || curr.axis != DESC || curr.uses(Use.POS)) continue;
// check if child steps can be retrieved for current step
ObjList<PathNode> pn = pathNodes(data, s);
if(pn == null) continue;
// cache child steps
final ObjList<QNm> qnm = new ObjList<QNm>();
while(pn.get(0).par != null) {
QNm nm = new QNm(data.tagindex.key(pn.get(0).name));
// skip children with prefixes
if(nm.hasPrefix()) return this;
for(int j = 0; j < pn.size(); ++j) {
if(pn.get(0).name != pn.get(j).name) nm = null;
}
qnm.add(nm);
pn = PathSummary.parent(pn);
}
ctx.compInfo(OPTCHILD, steps[s]);
// build new steps
int ts = qnm.size();
final Expr[] stps = new Expr[ts + steps.length - s - 1];
for(int t = 0; t < ts; ++t) {
final Expr[] preds = t == ts - 1 ?
((AxisStep) steps[s]).preds : new Expr[0];
final QNm nm = qnm.get(ts - t - 1);
final NameTest nt = nm == null ? new NameTest(false) :
new NameTest(nm, Name.NAME, false);
stps[t] = AxisStep.get(input, CHILD, nt, preds);
}
while(++s < steps.length) stps[ts++] = steps[s];
path = get(input, root, stps);
break;
}
// check if the all children in the path exist; don't test with namespaces
if(data.nspaces.size() == 0) {
LOOP:
for(int s = 0; s < path.steps.length; ++s) {
// only verify child steps; ignore namespaces
final AxisStep st = path.axisStep(s);
if(st == null || st.axis != CHILD) break;
if(st.test.test == Name.ALL || st.test.test == null) continue;
if(st.test.test != Name.NAME) break;
// check if one of the addressed nodes is on the correct level
final int name = data.tagindex.id(st.test.name.local());
for(final PathNode pn : data.paths.desc(name, Data.ELEM)) {
if(pn.level() == s + 1) continue LOOP;
}
ctx.compInfo(OPTPATH, path);
return Empty.SEQ;
}
}
return path;
}
/**
* Casts the specified step into an axis step, or returns a {@code null}
* reference.
* @param i index
* @return step
*/
AxisStep axisStep(final int i) {
return steps[i] instanceof AxisStep ? (AxisStep) steps[i] : null;
}
/**
* Returns all summary path nodes for the specified location step or
* {@code null} if nodes cannot be retrieved or are found on different levels.
* @param data data reference
* @param l last step to be checked
* @return path nodes
*/
ObjList<PathNode> pathNodes(final Data data, final int l) {
// skip request if no path index exists or might be out-of-date
if(!data.meta.pathindex || !data.meta.uptodate) return null;
ObjList<PathNode> in = data.paths.root();
for(int s = 0; s <= l; ++s) {
final AxisStep curr = axisStep(s);
if(curr == null) return null;
final boolean desc = curr.axis == DESC;
if(!desc && curr.axis != CHILD || curr.test.test != Name.NAME)
return null;
final int name = data.tagindex.id(curr.test.name.local());
final ObjList<PathNode> al = new ObjList<PathNode>();
for(final PathNode pn : PathSummary.desc(in, desc)) {
if(pn.kind == Data.ELEM && name == pn.name) {
// skip test if a tag is found on different levels
if(al.size() != 0 && al.get(0).level() != pn.level()) return null;
al.add(pn);
}
}
if(al.size() == 0) return null;
in = al;
}
return in;
}
/**
* Adds a predicate to the last step.
* @param pred predicate to be added
* @return resulting path instance
*/
public final Path addPreds(final Expr... pred) {
steps[steps.length - 1] = axisStep(steps.length - 1).addPreds(pred);
return get(input, root, steps);
}
@Override
public int count(final Var v) {
return root != null ? root.count(v) : 0;
}
@Override
public boolean removable(final Var v) {
return root == null || root.removable(v);
}
@Override
public Expr remove(final Var v) {
if(root != null) root = root.remove(v);
if(root instanceof Context) root = null;
return this;
}
@Override
public final void plan(final Serializer ser) throws IOException {
ser.openElement(this);
if(root != null) root.plan(ser);
for(final Expr s : steps) s.plan(ser);
ser.closeElement();
}
@Override
public final String toString() {
final StringBuilder sb = new StringBuilder();
if(root != null) sb.append(root);
for(final Expr s : steps) sb.append(sb.length() != 0 ? "/" : "").append(s);
return sb.toString();
}
}