package org.basex.query.func;
import static org.basex.query.QueryText.*;
import static org.basex.query.util.Err.*;
import static org.basex.util.Token.*;
import java.io.IOException;
import org.basex.core.User;
import org.basex.data.ExprInfo;
import org.basex.io.serial.Serializer;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.expr.Cast;
import org.basex.query.expr.Expr;
import org.basex.query.expr.VarRef;
import org.basex.query.item.AtomType;
import org.basex.query.item.FItem;
import org.basex.query.item.FuncItem;
import org.basex.query.item.FuncType;
import org.basex.query.item.QNm;
import org.basex.query.item.SeqType;
import org.basex.query.item.SeqType.Occ;
import org.basex.query.item.Type;
import org.basex.query.item.Types;
import org.basex.query.util.NSGlobal;
import org.basex.query.util.TypedFunc;
import org.basex.query.util.Var;
import org.basex.util.Array;
import org.basex.util.InputInfo;
import org.basex.util.Levenshtein;
/**
* Container for a user-defined function.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class UserFuncs extends ExprInfo {
/** Cached function calls. */
private UserFuncCall[][] calls = { };
/** Local functions. */
private UserFunc[] func = { };
/**
* Returns the number of functions.
* @return function
*/
public int size() {
return func.length;
}
/**
* Returns the specified function.
* @param name name of the function
* @param args optional arguments
* @param dyn compile-/run-time flag
* @param ctx query context
* @param ii input info
* @return function instance
* @throws QueryException query exception
*/
public TypedFunc get(final QNm name, final Expr[] args, final boolean dyn,
final QueryContext ctx, final InputInfo ii) throws QueryException {
// get namespace and local name
final byte[] uri = name.uri();
final byte[] ln = name.local();
// parse data type constructors
if(eq(uri, XSURI)) {
final Type type = AtomType.find(name, true);
if(type == null || type == AtomType.NOT || type == AtomType.AAT) {
final Levenshtein ls = new Levenshtein();
for(final AtomType t : AtomType.values()) {
if(t.par != null && ls.similar(lc(ln), lc(t.string()), 0))
FUNSIMILAR.thrw(ii, name.string(), t.string());
}
FUNCUNKNOWN.thrw(ii, name.string());
}
if(args.length != 1) FUNCTYPE.thrw(ii, name.string());
final SeqType to = SeqType.get(type, Occ.ZO);
return TypedFunc.constr(new Cast(ii, args[0], to), to);
}
// Java function (only allowed with administrator permissions)
if(startsWith(uri, JAVAPRE) && ctx.context.user.perm(User.ADMIN)) {
return TypedFunc.java(JavaMapping.get(name, args, ctx, ii));
}
// predefined functions
final StandardFunc fun = Functions.get().get(ln, uri, args, ctx, ii);
if(fun != null) {
for(final Function f : Function.UPDATING) {
if(fun.sig == f) {
ctx.updating = true;
break;
}
}
return new TypedFunc(fun, fun.sig.type(args.length));
}
// local function
for(int l = 0; l < func.length; ++l) {
final QNm qn = func[l].name;
if(eq(ln, qn.local()) && eq(uri, qn.uri()) && args.length ==
func[l].args.length) return new TypedFunc(
add(ii, qn, l, args), FuncType.get(func[l]));
}
// add function call for function that has not been defined yet
if(!dyn && Types.find(name, false) == null) {
return new TypedFunc(add(ii, name, add(new UserFunc(ii, name,
new Var[args.length], null, false), ii), args),
FuncType.arity(args.length));
}
return null;
}
/**
* Returns the specified function literal.
* @param name function name
* @param arity number of arguments
* @param dyn dynamic invocation flag
* @param ctx query context
* @param ii input info
* @return literal function expression
* @throws QueryException query exception
*/
public static FItem get(final QNm name, final long arity, final boolean dyn,
final QueryContext ctx, final InputInfo ii) throws QueryException {
final Expr[] args = new Expr[(int) arity];
final Var[] vars = new Var[args.length];
for(int i = 0; i < args.length; i++) {
vars[i] = ctx.uniqueVar(ii, null);
args[i] = new VarRef(ii, vars[i]);
}
final TypedFunc f = ctx.funcs.get(name, args, dyn, ctx, ii);
if(f == null) {
if(!dyn) FUNCUNKNOWN.thrw(ii, name + "#" + arity);
return null;
}
// compile the function if it hasn't been done statically
if(f.fun instanceof UserFuncCall) {
final UserFunc usf = ((UserFuncCall) f.fun).func();
if(usf != null && usf.declared) usf.comp(ctx);
}
final FuncType ft = f.type;
return new FuncItem(name, vars, f.fun, ft, false);
}
/**
* Registers and returns a new function call.
* @param ii input info
* @param nm function name
* @param id function id
* @param arg arguments
* @return new function call
*/
private UserFuncCall add(final InputInfo ii, final QNm nm, final int id,
final Expr[] arg) {
final UserFuncCall call = new BaseFuncCall(ii, nm, arg);
// for dynamic calls
if(func[id].declared) call.init(func[id]);
calls[id] = Array.add(calls[id], call);
return call;
}
/**
* Adds a local function.
* @param fun function instance
* @param ii input info
* @return function id
* @throws QueryException query exception
*/
public int add(final UserFunc fun, final InputInfo ii) throws QueryException {
final QNm name = fun.name;
final byte[] uri = name.uri();
if(uri.length == 0) FUNNONS.thrw(ii, name.string());
if(NSGlobal.reserved(uri)) {
if(fun.declared) NAMERES.thrw(ii, name.string());
funError(name, ii);
}
final byte[] ln = name.local();
for(int l = 0; l < func.length; ++l) {
final QNm qn = func[l].name;
final byte[] u = qn.uri();
final byte[] nm = qn.local();
if(eq(ln, nm) && eq(uri, u) && fun.args.length == func[l].args.length) {
// declare function that has been called before
if(!func[l].declared) {
func[l] = fun;
return l;
}
// duplicate declaration
FUNCDEFINED.thrw(ii, fun);
}
}
// add function skeleton
func = Array.add(func, fun);
calls = Array.add(calls, new UserFuncCall[0]);
return func.length - 1;
}
/**
* Checks if all functions have been correctly declared, and initializes
* all function calls.
* @throws QueryException query exception
*/
public void check() throws QueryException {
// initialize function calls
for(int i = 0; i < func.length; ++i) {
for(final UserFuncCall c : calls[i]) c.init(func[i]);
}
for(final UserFunc f : func) {
if(!f.declared || f.expr == null) {
// function has not been declare yet
for(final UserFunc uf : func) {
// check if another function with same name exists
if(f != uf && f.name.eq(uf.name)) {
FUNCTYPE.thrw(f.input, uf.name.string());
}
}
// if not, indicate that function is unknown
FUNCUNKNOWN.thrw(f.input, f.name.string());
}
f.checkUp();
}
}
/**
* Compiles the functions.
* @param ctx query context
* @throws QueryException query exception
*/
public void comp(final QueryContext ctx) throws QueryException {
// only compile those functions that are used
for(int i = 0; i < func.length; ++i) {
if(calls[i].length != 0) func[i].comp(ctx);
}
}
/**
* Finds similar function names and throws an error message.
* @param name function name
* @param ii input info
* @throws QueryException query exception
*/
public void funError(final QNm name, final InputInfo ii)
throws QueryException {
// find global function
Functions.get().error(name, ii);
// find similar local function
final Levenshtein ls = new Levenshtein();
final byte[] nm = lc(name.local());
for(final UserFunc f : func) {
if(ls.similar(nm, lc(f.name.local()), 0)) {
FUNSIMILAR.thrw(ii, name.string(), f.name.string());
}
}
}
@Override
public void plan(final Serializer ser) throws IOException {
if(func.length == 0) return;
ser.openElement(this);
for(final UserFunc f : func) f.plan(ser);
ser.closeElement();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
for(final UserFunc f : func) sb.append(f.toString());
return sb.toString();
}
}