package org.basex.query;
import static org.basex.util.Token.*;
import java.util.Stack;
import org.basex.data.Data;
import org.basex.index.path.*;
import org.basex.query.path.Axis;
import org.basex.query.path.Test;
import org.basex.query.util.Err;
import org.basex.util.TokenBuilder;
import org.basex.util.list.ObjList;
import org.basex.util.list.StringList;
/**
* This class analyzes the current path and gives suggestions for code
* completions.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class QuerySuggest extends QueryParser {
/** Data reference. */
private final Data data;
/** All current path nodes. */
private Stack<ObjList<PathNode>> stack;
/** All current path nodes. */
private ObjList<PathNode> all;
/** Current path nodes. */
private ObjList<PathNode> curr;
/** Hide flag. */
private boolean show;
/** Last tag name. */
private byte[] tag;
/**
* Constructor.
* @param q query
* @param c query context
* @param d data reference
* @throws QueryException query exception
*/
public QuerySuggest(final String q, final QueryContext c, final Data d)
throws QueryException {
super(q, c);
data = d;
checkInit();
}
/**
* Sorts and returns the query suggestions.
* @return completions
*/
public StringList complete() {
final StringList sl = new StringList();
if(show) {
for(final PathNode n : curr) {
final String nm = string(n.token(data));
if(!nm.isEmpty() && !sl.contains(nm)) sl.add(nm);
}
sl.sort(true, true);
}
return sl;
}
@Override
protected void checkInit() {
if(stack != null && !stack.empty() || !data.meta.pathindex) return;
all = data.paths.root();
curr = all;
stack = new Stack<ObjList<PathNode>>();
}
@Override
protected void checkAxis(final Axis axis) {
all = axis != Axis.CHILD && axis != Axis.DESC || !data.meta.pathindex ?
new ObjList<PathNode>() : PathSummary.desc(curr, axis == Axis.DESC);
curr = all;
show = true;
}
@Override
protected void checkTest(final Test test, final boolean attr) {
final TokenBuilder tb = new TokenBuilder();
if(attr) tb.add('@');
if(test != null) tb.add(test.toString().replaceAll("\\*:", ""));
tag = tb.finish();
// use inexact matching only, if the tag is at the end:
checkTest(qp < ql);
}
/**
* Checks the tag name.
* @param eq equality test
*/
private void checkTest(final boolean eq) {
if(tag == null) return;
final ObjList<PathNode> tmp = new ObjList<PathNode>();
boolean s = false;
for(final PathNode p : all) {
final byte[] nm = p.token(data);
if(startsWith(nm, tag)) {
if(!eq || eq(nm, tag)) tmp.add(p);
s |= !eq(tag, nm);
}
}
show = tag.length == 0 || s;
curr = tmp;
}
@Override
protected void checkPred(final boolean open) {
if(stack == null) return;
if(open) {
checkTest(true);
final ObjList<PathNode> tmp = new ObjList<PathNode>();
for(final PathNode p : curr) tmp.add(p);
stack.add(tmp);
checkAxis(Axis.CHILD);
} else {
curr = stack.pop();
show = false;
all = curr;
}
}
@Override
public QueryException error(final Err err, final Object... arg)
throws QueryException {
throw new QueryException(input(), err, arg).suggest(this, complete());
}
}