package org.basex.query.expr;
import static org.basex.query.QueryText.*;
import static org.basex.query.util.Err.*;
import java.io.IOException;
import org.basex.index.IndexToken.IndexType;
import org.basex.index.ValuesToken;
import org.basex.io.serial.Serializer;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.func.Function;
import org.basex.query.item.AtomType;
import org.basex.query.item.Bln;
import org.basex.query.item.Item;
import org.basex.query.item.NodeType;
import org.basex.query.item.SeqType;
import org.basex.query.item.Type;
import org.basex.query.iter.ItemCache;
import org.basex.query.iter.Iter;
import org.basex.query.path.AxisPath;
import org.basex.query.path.AxisStep;
import org.basex.query.util.IndexContext;
import org.basex.util.Array;
import org.basex.util.InputInfo;
import org.basex.util.Token;
/**
* General comparison.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class CmpG extends Cmp {
/** Comparators. */
public enum Op {
/** General comparison: less or equal. */
LE("<=", CmpV.Op.LE) {
@Override
public Op swap() { return Op.GE; }
@Override
public Op invert() { return Op.LT; }
},
/** General comparison: less. */
LT("<", CmpV.Op.LT) {
@Override
public Op swap() { return Op.GT; }
@Override
public Op invert() { return Op.GE; }
},
/** General comparison: greater of equal. */
GE(">=", CmpV.Op.GE) {
@Override
public Op swap() { return Op.LE; }
@Override
public Op invert() { return Op.LT; }
},
/** General comparison: greater. */
GT(">", CmpV.Op.GT) {
@Override
public Op swap() { return Op.LT; }
@Override
public Op invert() { return Op.LE; }
},
/** General comparison: equal. */
EQ("=", CmpV.Op.EQ) {
@Override
public Op swap() { return Op.EQ; }
@Override
public Op invert() { return Op.NE; }
},
/** General comparison: not equal. */
NE("!=", CmpV.Op.NE) {
@Override
public Op swap() { return Op.NE; }
@Override
public Op invert() { return Op.EQ; }
};
/** String representation. */
public final String name;
/** Comparator. */
final CmpV.Op op;
/**
* Constructor.
* @param n string representation
* @param c comparator
*/
Op(final String n, final CmpV.Op c) {
name = n;
op = c;
}
/**
* Swaps the comparator.
* @return swapped comparator
*/
public abstract Op swap();
/**
* Inverts the comparator.
* @return inverted comparator
*/
public abstract Op invert();
@Override
public String toString() { return name; }
}
/** Comparator. */
Op op;
/** Index expression. */
private IndexAccess[] iacc = {};
/** Flag for atomic evaluation. */
private boolean atomic;
/**
* Constructor.
* @param ii input info
* @param e1 first expression
* @param e2 second expression
* @param o operator
*/
public CmpG(final InputInfo ii, final Expr e1, final Expr e2, final Op o) {
super(ii, e1, e2);
op = o;
}
@Override
public Expr comp(final QueryContext ctx) throws QueryException {
super.comp(ctx);
// swap expressions; add text() to location paths to simplify optimizations
if(swap()) {
op = op.swap();
ctx.compInfo(OPTSWAP, this);
}
for(int e = 0; e != expr.length; ++e) expr[e] = expr[e].addText(ctx);
final Expr e1 = expr[0];
final Expr e2 = expr[1];
Expr e = this;
if(oneIsEmpty()) {
e = optPre(Bln.FALSE, ctx);
} else if(allAreValues()) {
e = preEval(ctx);
} else if(e1.isFunction(Function.COUNT)) {
e = compCount(op.op);
if(e != this) ctx.compInfo(e instanceof Bln ? OPTPRE : OPTWRITE, this);
} else if(e1.isFunction(Function.POSITION)) {
if(e2 instanceof Range && op.op == CmpV.Op.EQ) {
// position() CMP range
final long[] rng = ((Range) e2).range(ctx);
e = rng == null ? this : Pos.get(rng[0], rng[1], input);
} else {
// position() CMP number
e = Pos.get(op.op, e2, e, input);
}
if(e != this) ctx.compInfo(OPTWRITE, this);
} else if(e1.type().eq(SeqType.BLN) && (op == Op.EQ && e2 == Bln.FALSE ||
op == Op.NE && e2 == Bln.TRUE)) {
// (A = false()) -> not(A)
e = Function.NOT.get(input, e1);
} else {
// rewrite path CMP number
e = CmpR.get(this);
if(e != this) ctx.compInfo(OPTWRITE, this);
}
// check if both arguments will always yield one result
atomic = e1.type().zeroOrOne() && e2.type().zeroOrOne();
type = SeqType.BLN;
return e;
}
@Override
public Expr compEbv(final QueryContext ctx) {
// e.g.: exists(...) = true() -> exists(...)
// checking one direction is sufficient, as operators may have been swapped
return (op == Op.EQ && expr[1] == Bln.TRUE ||
op == Op.NE && expr[1] == Bln.FALSE) &&
expr[0].type().eq(SeqType.BLN) ? expr[0] : this;
}
@Override
public Bln item(final QueryContext ctx, final InputInfo ii)
throws QueryException {
// atomic evaluation of arguments (faster)
if(atomic) {
final Item it1 = expr[0].item(ctx, input);
if(it1 == null) return Bln.FALSE;
final Item it2 = expr[1].item(ctx, input);
if(it2 == null) return Bln.FALSE;
return Bln.get(eval(it1, it2));
}
final Iter ir1 = ctx.iter(expr[0]);
final long is1 = ir1.size();
// skip empty result
if(is1 == 0) return Bln.FALSE;
final boolean s1 = is1 == 1;
// evaluate single items
if(s1 && expr[1].size() == 1)
return Bln.get(eval(ir1.next(), expr[1].item(ctx, input)));
Iter ir2 = ctx.iter(expr[1]);
final long is2 = ir2.size();
// skip empty result
if(is2 == 0) return Bln.FALSE;
final boolean s2 = is2 == 1;
// evaluate single items
if(s1 && s2) return Bln.get(eval(ir1.next(), ir2.next()));
// evaluate iterator and single item
Item it1, it2;
if(s2) {
it2 = ir2.next();
while((it1 = ir1.next()) != null) if(eval(it1, it2)) return Bln.TRUE;
return Bln.FALSE;
}
// evaluate two iterators
if(!ir2.reset()) {
// cache items for next comparisons
final ItemCache ic = new ItemCache();
if((it1 = ir1.next()) != null) {
while((it2 = ir2.next()) != null) {
if(eval(it1, it2)) return Bln.TRUE;
ic.add(it2);
}
}
ir2 = ic;
}
while((it1 = ir1.next()) != null) {
ir2.reset();
while((it2 = ir2.next()) != null) if(eval(it1, it2)) return Bln.TRUE;
}
return Bln.FALSE;
}
/**
* Compares a single item.
* @param a first item to be compared
* @param b second item to be compared
* @return result of check
* @throws QueryException query exception
*/
private boolean eval(final Item a, final Item b) throws QueryException {
final Type ta = a.type;
final Type tb = b.type;
if(ta != tb && (!ta.isUntyped() && !tb.isUntyped() && !(ta.isString() &&
tb.isString()) && !(ta.isNumber() && tb.isNumber()) &&
!ta.isFunction() && !tb.isFunction() ||
ta == AtomType.QNM || tb == AtomType.QNM))
XPTYPECMP.thrw(input, ta, tb);
return op.op.eval(input, a, b);
}
@Override
public CmpG invert() {
return expr[0].size() != 1 || expr[1].size() != 1 ? this :
new CmpG(input, expr[0], expr[1], op.invert());
}
/**
* Creates a union of the existing and the specified expressions.
* @param g general comparison
* @param ctx query context
* @return true if union was successful
* @throws QueryException query exception
*/
boolean union(final CmpG g, final QueryContext ctx) throws QueryException {
if(op != g.op || !expr[0].sameAs(g.expr[0])) return false;
expr[1] = new List(input, expr[1], g.expr[1]).comp(ctx);
atomic = atomic && expr[1].type().zeroOrOne();
return true;
}
@Override
public boolean indexAccessible(final IndexContext ic) throws QueryException {
// accept only location path, string and equality expressions
if(op != Op.EQ) return false;
final AxisStep s = expr[0] instanceof Context ?
ic.step : indexStep(expr[0]);
if(s == null) return false;
// check which index applies
final boolean text = s.test.type == NodeType.TXT && ic.data.meta.textindex;
final boolean attr = s.test.type == NodeType.ATT && ic.data.meta.attrindex;
if(!text && !attr) return false;
// support expressions
final IndexType ind = text ? IndexType.TEXT : IndexType.ATTRIBUTE;
final Expr arg = expr[1];
if(!arg.isValue()) {
final SeqType t = arg.type();
/* index access is not possible if returned type is no string or node, if
expression depends on context, or if it is non-deterministic. examples:
//*[text() = 1]
//*[text() = .]
//*[text() = (if(math:random() < .5) then 'X' else 'Y')]
*/
if(!t.type.isString() && !t.type.isNode() ||
arg.uses(Use.CTX) || arg.uses(Use.NDT)) return false;
ic.addCosts(ic.data.meta.size / 10);
iacc = Array.add(iacc, new IndexAccess(input, arg, ind, ic));
return true;
}
// loop through all items
final Iter ir = arg.iter(ic.ctx);
Item it;
ic.costs(0);
while((it = ir.next()) != null) {
final SeqType t = it.type();
if(!(t.type.isString() || t.type.isNode())) return false;
final int is = ic.data.count(new ValuesToken(ind, it.string(input)));
// add only expressions that yield results
if(is != 0) {
iacc = Array.add(iacc, new IndexAccess(input, it, ind, ic));
ic.addCosts(is);
}
}
return true;
}
@Override
public Expr indexEquivalent(final IndexContext ic) {
// will only be called for costs != 0
final boolean text = iacc[0].itype == IndexType.TEXT;
ic.ctx.compInfo(text ? OPTTXTINDEX : OPTATVINDEX);
// more than one string - merge index results
final ParseExpr root = iacc.length == 1 ? iacc[0] : new Union(input, iacc);
return ic.invert(expr[0], root, text);
}
/**
* If possible, returns the last location step of the specified expression.
* @param expr expression
* @return location step
*/
public static AxisStep indexStep(final Expr expr) {
// check if index can be applied
if(!(expr instanceof AxisPath)) return null;
// accept only single axis steps as first expression
final AxisPath path = (AxisPath) expr;
// path must contain no root node
return path.root != null ? null : path.step(path.steps.length - 1);
}
@Override
public void plan(final Serializer ser) throws IOException {
ser.openElement(this, OP, Token.token(op.name));
for(final Expr e : expr) e.plan(ser);
ser.closeElement();
}
@Override
public String description() {
return "'" + op + "' expression";
}
@Override
public String toString() {
return toString(" " + op + ' ');
}
}