package org.basex.query.func;
import static org.basex.query.util.Err.*;
import static org.basex.util.Token.*;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
import java.util.zip.CRC32;
import org.basex.core.Prop;
import org.basex.io.IO;
import org.basex.io.in.NewlineInput;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.expr.Expr;
import org.basex.query.item.AtomType;
import org.basex.query.item.Bln;
import org.basex.query.item.Hex;
import org.basex.query.item.Int;
import org.basex.query.item.IntSeq;
import org.basex.query.item.Item;
import org.basex.query.item.Str;
import org.basex.query.item.Value;
import org.basex.query.iter.Iter;
import org.basex.query.iter.ValueIter;
import org.basex.query.util.Compare;
import org.basex.query.util.Compare.Flag;
import org.basex.util.Array;
import org.basex.util.InputInfo;
import org.basex.util.Performance;
import org.basex.util.Util;
import org.basex.util.list.ByteList;
/**
* Project specific functions.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
* @author Leo Woerteler
*/
public final class FNUtil extends StandardFunc {
/**
* Constructor.
* @param ii input info
* @param f function definition
* @param e arguments
*/
public FNUtil(final InputInfo ii, final Function f, final Expr... e) {
super(ii, f, e);
}
@Override
public Iter iter(final QueryContext ctx) throws QueryException {
switch(sig) {
case _UTIL_EVAL: return eval(ctx).iter();
case _UTIL_RUN: return run(ctx).iter();
case _UTIL_MEM: return mem(ctx);
case _UTIL_TIME: return time(ctx);
case _UTIL_TO_BYTES: return toBytes(ctx);
default: return super.iter(ctx);
}
}
@Override
public Value value(final QueryContext ctx) throws QueryException {
switch(sig) {
case _UTIL_EVAL: return eval(ctx);
case _UTIL_RUN: return run(ctx);
default: return super.value(ctx);
}
}
@Override
public Item item(final QueryContext ctx, final InputInfo ii)
throws QueryException {
switch(sig) {
case _UTIL_FORMAT: return format(ctx);
case _UTIL_INTEGER_FROM_BASE: return fromBase(ctx, ii);
case _UTIL_INTEGER_TO_BASE: return toBase(ctx, ii);
case _UTIL_MD5: return hash(ctx, "MD5");
case _UTIL_SHA1: return hash(ctx, "SHA");
case _UTIL_CRC32: return crc32(ctx);
case _UTIL_UUID: return uuid();
case _UTIL_TO_STRING: return toString(ctx);
case _UTIL_DEEP_EQUAL: return deep(ctx);
case _UTIL_PATH: return filename(ctx);
default: return super.item(ctx, ii);
}
}
/**
* Performs the eval function.
* @param ctx query context
* @return resulting value
* @throws QueryException query exception
*/
private Value eval(final QueryContext ctx) throws QueryException {
return eval(ctx, checkEStr(expr[0], ctx));
}
/**
* Evaluates the specified string.
* @param ctx query context
* @param qu query string
* @return resulting value
* @throws QueryException query exception
*/
private static Value eval(final QueryContext ctx, final byte[] qu)
throws QueryException {
final QueryContext qc = new QueryContext(ctx.context);
qc.parse(string(qu));
qc.compile();
return qc.value();
}
/**
* Performs the run function.
* @param ctx query context
* @return resulting value
* @throws QueryException query exception
*/
private Value run(final QueryContext ctx) throws QueryException {
final IO io = checkIO(expr[0], ctx);
try {
return eval(ctx, io.read());
} catch(final IOException ex) {
throw IOERR.thrw(input, ex);
}
}
/**
* Formats a string according to the specified format.
* @param ctx query context
* @return formatted string
* @throws QueryException query exception
*/
private Str format(final QueryContext ctx) throws QueryException {
final String form = string(checkStr(expr[0], ctx));
final Object[] args = new Object[expr.length - 1];
for(int e = 1; e < expr.length; e++) {
args[e - 1] = expr[e].item(ctx, input).toJava();
}
try {
return Str.get(String.format(form, args));
} catch(final RuntimeException ex) {
throw ERRFORM.thrw(input, Util.name(ex), ex.getMessage());
}
}
/**
* Measures the memory consumption for the specified expression in MB.
* @param ctx query context
* @return memory consumption
* @throws QueryException query exception
*/
private Iter mem(final QueryContext ctx) throws QueryException {
// measure initial memory consumption
Performance.gc(3);
final long min = Performance.mem();
// check caching flag
if(expr.length == 2 && checkBln(expr[1], ctx)) {
final Value v = ctx.value(expr[0]).cache().value();
dump(min, ctx);
return v.iter();
}
return new Iter() {
final Iter ir = expr[0].iter(ctx);
@Override
public Item next() throws QueryException {
final Item i = ir.next();
if(i == null) dump(min, ctx);
return i;
}
};
}
/**
* Dumps the memory consumption.
* @param min initial memory usage
* @param ctx query context
*/
static void dump(final long min, final QueryContext ctx) {
Performance.gc(2);
final long max = Performance.mem();
final long mb = Math.max(0, max - min);
FNInfo.dump(Performance.format(mb), ctx);
}
/**
* Measures the execution time for the specified expression in milliseconds.
* @param ctx query context
* @return time in milliseconds
* @throws QueryException query exception
*/
private Iter time(final QueryContext ctx) throws QueryException {
// create timer
final Performance p = new Performance();
// check caching flag
if(expr.length == 2 && checkBln(expr[1], ctx)) {
final Value v = ctx.value(expr[0]).cache().value();
FNInfo.dump(p.getTimer(), ctx);
return v.iter();
}
return new Iter() {
final Iter ir = expr[0].iter(ctx);
@Override
public Item next() throws QueryException {
final Item i = ir.next();
if(i == null) FNInfo.dump(p.getTimer(), ctx);
return i;
}
};
}
/** Digits used in base conversion. */
private static final byte[] DIGITS = {
'0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};
/**
* Converts the given number to a string, using base
* 2<sup>shift</sup>.
* @param num number item
* @param shift number of bits to use for one digit
* @return string representation of the given number
*/
private static Str toBaseFast(final long num, final int shift) {
final byte[] bytes = new byte[(64 + shift - 1) / shift];
final int mask = (1 << shift) - 1;
long n = num;
int pos = bytes.length;
do {
bytes[--pos] = DIGITS[(int) (n & mask)];
n >>>= shift;
} while(n != 0);
return Str.get(substring(bytes, pos));
}
/** BigInteger representing 2 * ({@link Long#MAX_VALUE} + 1). */
private static final BigInteger MAX_ULONG = BigInteger.ONE.shiftLeft(64);
/**
* Converts the given number to a string, using the given base.
* @param ctx query context
* @param ii input info
* @return string representation of the given number
* @throws QueryException query exception
*/
private Str toBase(final QueryContext ctx, final InputInfo ii)
throws QueryException {
final long num = checkItr(expr[0], ctx), base = checkItr(expr[1], ctx);
if(base < 2 || base > 36) INVBASE.thrw(ii, base);
// use fast variant for powers of two
for(int i = 1, p = 2; i < 6; i++, p <<= 1)
if(base == p) return toBaseFast(num, i);
final ByteList tb = new ByteList();
long n = num;
if(n < 0) {
// unsigned value doesn't fit in any native type...
final BigInteger[] dr = BigInteger.valueOf(n).add(
MAX_ULONG).divideAndRemainder(BigInteger.valueOf(base));
n = dr[0].longValue();
tb.add(DIGITS[dr[1].intValue()]);
} else {
tb.add(DIGITS[(int) (n % base)]);
n /= base;
}
while (n != 0) {
tb.add(DIGITS[(int) (n % base)]);
n /= base;
}
final byte[] res = tb.toArray();
Array.reverse(res);
return Str.get(res);
}
/**
* Converts the given string to a number, interpreting it as an xs:integer
* encoded in the given base.
* @param ctx query context
* @param ii input info
* @return read integer
* @throws QueryException exception
*/
private Int fromBase(final QueryContext ctx, final InputInfo ii)
throws QueryException {
final byte[] str = checkStr(expr[0], ctx);
final long base = checkItr(expr[1], ctx);
if(base < 2 || base > 36) INVBASE.thrw(ii, base);
long res = 0;
for(final byte b : str) {
final int num = b <= '9' ? b - 0x30 : (b & 0xDF) - 0x37;
if(!(b >= '0' && b <= '9' || b >= 'a' && b <= 'z' ||
b >= 'A' && b <= 'Z') || num >= base)
INVDIG.thrw(ii, base, (char) (b & 0xff));
res = res * base + num;
}
return Int.get(res);
}
/**
* Creates the hash of the given xs:string, using the algorithm {@code algo}.
* @param ctx query context
* @param algo hashing algorithm
* @return xs:hexBinary instance containing the hash
* @throws QueryException exception
*/
private Hex hash(final QueryContext ctx, final String algo)
throws QueryException {
final byte[] str = checkStr(expr[0], ctx);
try {
return new Hex(MessageDigest.getInstance(algo).digest(str));
} catch(final NoSuchAlgorithmException ex) {
throw Util.notexpected(ex);
}
}
/**
* Creates the CRC32 hash of the given xs:string.
* @param ctx query context
* @return xs:hexBinary instance containing the hash
* @throws QueryException exception
*/
private Hex crc32(final QueryContext ctx) throws QueryException {
final CRC32 crc = new CRC32();
crc.update(checkStr(expr[0], ctx));
final byte[] res = new byte[4];
for(int i = res.length, c = (int) crc.getValue(); i-- > 0; c >>>= 8)
res[i] = (byte) (c & 0xFF);
return new Hex(res);
}
/**
* Extracts the bytes from a given item.
* @param ctx query context
* @return resulting value
* @throws QueryException query exception
*/
private Iter toBytes(final QueryContext ctx) throws QueryException {
final Item it = checkItem(expr[0], ctx);
final ByteList bl = new ByteList();
final InputStream is = it.input(input);
try {
try {
for(int ch; (ch = is.read()) != -1;) bl.add(ch);
} finally {
is.close();
}
} catch(final IOException ex) {
CONVERT.thrw(input, ex);
}
return new ValueIter() {
final int bs = bl.size();
int pos;
@Override
public Value value() {
final long[] tmp = new long[bs - pos];
for(int i = 0; i < tmp.length; i++) tmp[i] = bl.get(pos + i);
return IntSeq.get(tmp, AtomType.BYT);
}
@Override
public Item get(final long i) {
return Int.get(bl.get((int) i), AtomType.BYT);
}
@Override
public Item next() { return pos < size() ? get(pos++) : null; }
@Override
public boolean reset() { pos = 0; return true; }
@Override
public long size() { return bs; }
};
}
/**
* Converts the specified data to a string.
* @param ctx query context
* @return resulting value
* @throws QueryException query exception
*/
private Str toString(final QueryContext ctx) throws QueryException {
final Item it = checkItem(expr[0], ctx);
final String enc = expr.length == 2 ? string(checkStr(expr[1], ctx)) : UTF8;
try {
final InputStream is = it.input(input);
try {
return Str.get(new NewlineInput(is, enc).content());
} finally {
is.close();
}
} catch(final IOException ex) {
throw CONVERT.thrw(input, ex);
}
}
/**
* Creates a random UUID.
* @return random UUID
*/
private static Str uuid() {
return Str.get(UUID.randomUUID());
}
/**
* Checks items for deep equality.
* @param ctx query context
* @return result of check
* @throws QueryException query exception
*/
private Item deep(final QueryContext ctx) throws QueryException {
final Compare cmp = new Compare(input);
final Flag[] flags = Flag.values();
if(expr.length == 3) {
final Iter ir = expr[2].iter(ctx);
for(Item it; (it = ir.next()) != null;) {
final byte[] key = uc(checkEStr(it));
boolean found = false;
for(final Flag f : flags) {
found = eq(key, token(f.name()));
if(found) {
cmp.set(f);
break;
}
}
if(!found) INVFLAG.thrw(input, key);
}
}
return Bln.get(cmp.deep(ctx.iter(expr[0]), ctx.iter(expr[1])));
}
/**
* Returns the name of the query file, or {@code null} if none is given.
* @param ctx query context
* @return filename
*/
private static Str filename(final QueryContext ctx) {
final String fn = ctx.context.prop.get(Prop.QUERYPATH);
return fn.isEmpty() ? null : Str.get(fn);
}
@Override
public boolean uses(final Use u) {
return u == Use.NDT && (sig == Function._UTIL_EVAL ||
sig == Function._UTIL_RUN || sig == Function._UTIL_MEM ||
sig == Function._UTIL_TIME || sig == Function._UTIL_UUID) ||
super.uses(u);
}
}