package org.basex.query.util;
import static org.basex.query.QueryText.*;
import static org.basex.query.util.Err.*;
import java.io.IOException;
import org.basex.io.serial.Serializer;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.expr.Expr;
import org.basex.query.expr.ParseExpr;
import org.basex.query.item.Item;
import org.basex.query.item.QNm;
import org.basex.query.item.SeqType;
import org.basex.query.item.Value;
import org.basex.query.iter.Iter;
import org.basex.util.InputInfo;
import org.basex.util.Token;
import org.basex.util.TokenBuilder;
/**
* Variable expression.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class Var extends ParseExpr {
/** Variable name. */
public final QNm name;
/** Variable ID. */
private final int id;
/** Expected return type. */
public SeqType ret;
/** Global flag. */
public boolean global;
/** Declaration flag. */
public boolean declared;
/** Bound value. */
private Value value;
/** Bound expression. */
private Expr expr;
/**
* Constructor.
* @param ii input info
* @param n variable name
* @param t data type
* @param i variable ID
*/
private Var(final InputInfo ii, final QNm n, final SeqType t, final int i) {
super(ii);
name = n;
type = t;
id = i;
}
/**
* Creates a new variable.
* @param ctx query context
* @param ii input info
* @param n variable name
* @param t type
* @return variable
*/
public static Var create(final QueryContext ctx, final InputInfo ii,
final QNm n, final SeqType t) {
return new Var(ii, n, t, ctx.varIDs++);
}
/**
* Creates a new variable.
* @param ctx query context
* @param ii input info
* @param n variable name
* @return variable
*/
public static Var create(final QueryContext ctx, final InputInfo ii,
final QNm n) {
return create(ctx, ii, n, (SeqType) null);
}
/**
* Creates a new variable.
* @param ctx query context
* @param ii input info
* @param n variable name
* @param v variable to be bound
* @return variable
*/
public static Var create(final QueryContext ctx, final InputInfo ii,
final QNm n, final Value v) {
final Var var = create(ctx, ii, n, v.type());
var.expr = v;
var.value = v;
return var;
}
/**
* Checks if the variable contains no updating expression.
* @throws QueryException query exception
*/
public void checkUp() throws QueryException {
if(expr != null && expr.uses(Use.UPD)) UPNOT.thrw(input, description());
}
@Override
public Var comp(final QueryContext ctx) throws QueryException {
if(expr != null) bind(checkUp(expr, ctx).comp(ctx), ctx);
return this;
}
/**
* Sets the specified variable type.
* @param t type
* @param ctx query context
* @throws QueryException query exception
*/
public void reset(final SeqType t, final QueryContext ctx)
throws QueryException {
type = t;
if(value != null && !value.type.instanceOf(t.type) &&
value instanceof Item) {
value = type.type.cast((Item) value, ctx, input);
}
}
/**
* Binds the specified expression to the variable.
* @param e expression to be set
* @param ctx query context
* @return self reference
* @throws QueryException query exception
*/
public Var bind(final Expr e, final QueryContext ctx) throws QueryException {
expr = e;
return e.isValue() ? bind((Value) e, ctx) : this;
}
/**
* Returns the bound expression.
* @return expression
*/
public Expr expr() {
return expr;
}
/**
* Binds the specified value to the variable.
* @param v value to be set
* @param ctx query context
* @return self reference
* @throws QueryException query exception
*/
public Var bind(final Value v, final QueryContext ctx) throws QueryException {
expr = v;
value = cast(v, ctx);
return this;
}
@Override
public Item item(final QueryContext ctx, final InputInfo ii)
throws QueryException {
return value(ctx).item(ctx, ii);
}
@Override
public Iter iter(final QueryContext ctx) throws QueryException {
return value(ctx).iter();
}
@Override
public Value value(final QueryContext ctx) throws QueryException {
if(value == null) {
if(expr == null) VAREMPTY.thrw(input, this);
final Value v = ctx.value;
ctx.value = null;
try {
value = cast(ctx.value(expr.comp(ctx)), ctx);
} finally {
ctx.value = v;
}
}
return value;
}
/**
* Checks whether the given variable is identical to this one, i.e. has the
* same ID.
* @param v variable to check
* @return {@code true}, if the IDs are equal, {@code false} otherwise
*/
public boolean is(final Var v) {
return id == v.id;
}
/**
* If necessary, casts the specified value if a type is specified.
* @param v input value
* @param ctx query context
* @return cast value
* @throws QueryException query exception
*/
private Value cast(final Value v, final QueryContext ctx)
throws QueryException {
return type == null ? v : type.promote(v, ctx, input);
}
/**
* Returns a copy of the variable.
* @return copy
*/
public Var copy() {
final Var v = new Var(input, name, type, id);
v.global = global;
v.value = value;
v.expr = expr;
v.type = type;
v.ret = ret;
return v;
}
@Override
public boolean uses(final Use u) {
return u == Use.VAR;
}
@Override
public int count(final Var v) {
return is(v) ? 1 : 0;
}
@Override
public boolean removable(final Var v) {
// only VarRefs can be removed
return false;
}
@Override
public Var remove(final Var v) {
return this;
}
@Override
public SeqType type() {
return ret != null ? ret : type != null ? type :
expr != null ? expr.type() : SeqType.ITEM_ZM;
}
@Override
public boolean sameAs(final Expr cmp) {
if(!(cmp instanceof Var)) return false;
final Var v = (Var) cmp;
return name.eq(v.name) && type().eq(v.type());
}
@Override
public void plan(final Serializer ser) throws IOException {
ser.openElement(this, NAM, Token.token(toString()));
if(expr != null) expr.plan(ser);
ser.closeElement();
}
@Override
public String toString() {
final TokenBuilder tb = new TokenBuilder();
if(name != null) {
tb.add(DOLLAR).add(name.string());
if(type != null) tb.add(' ' + AS);
}
if(type != null) tb.add(" " + type);
return tb.toString();
}
}