/** * Checks if the specified step will never yield results. * * @param rt root value * @param s index of step * @return {@code true} if steps will never yield results */ private boolean emptyPath(final Value rt, final int s) { final Step step = axisStep(s); if (step == null) return false; final Axis axis = step.axis; if (s == 0) { // first location step: if (root instanceof CAttr) { // @.../child:: / @.../descendant:: if (axis == CHILD || axis == DESC) return true; } else if (root instanceof Root || root instanceof CDoc || rt != null && rt.type == NodeType.DOC) { if (axis == SELF || axis == ANCORSELF) { if (step.test != Test.NOD && step.test != Test.DOC) return true; } else if (axis == CHILD || axis == DESC) { if (step.test == Test.DOC || step.test == Test.ATT) return true; } else if (axis == DESCORSELF) { if (step.test == Test.ATT) return true; } else { return true; } } } else { // remaining steps: final Step last = axisStep(s - 1); if (last == null) return false; // .../self:: / .../descendant-or-self:: if (axis == SELF || axis == DESCORSELF) { if (step.test == Test.NOD) return false; // @.../..., text()/... if (last.axis == ATTR && step.test.type != NodeType.ATT || last.test == Test.TXT && step.test != Test.TXT) return true; if (axis == DESCORSELF) return false; // .../self:: final QNm name = step.test.name, lastName = last.test.name; if (lastName == null || name == null || lastName.local().length == 0 || name.local().length == 0) return false; // ...X/...Y return !name.eq(lastName); } // .../following-sibling:: / .../preceding-sibling:: if (axis == FOLLSIBL || axis == PRECSIBL) return last.axis == ATTR; // .../descendant:: / .../child:: / .../attribute:: if (axis == DESC || axis == CHILD || axis == ATTR) return last.axis == ATTR || last.test == Test.TXT || last.test == Test.COM || last.test == Test.PI || axis == ATTR && step.test == Test.NSP; // .../parent:: / .../ancestor:: if (axis == PARENT || axis == ANC) return last.test == Test.DOC; } return false; }
/** * Converts descendant to child steps. * * @param qc query context * @param rt root value * @return original or new expression */ private Expr children(final QueryContext qc, final Value rt) { // skip if index does not exist or is out-dated, or if several namespaces occur in the input final Data data = rt.data(); if (data == null || !data.meta.uptodate || data.nspaces.globalNS() == null) return this; Path path = this; final int sl = steps.length; for (int s = 0; s < sl; s++) { // don't allow predicates in preceding location steps final Step prev = s > 0 ? axisStep(s - 1) : null; if (prev != null && prev.preds.length != 0) break; // ignore axes other than descendant, or numeric predicates final Step curr = axisStep(s); if (curr == null || curr.axis != DESC || curr.has(Flag.FCS)) continue; // check if child steps can be retrieved for current step ArrayList<PathNode> nodes = pathNodes(data, s); if (nodes == null) continue; // cache child steps final ArrayList<QNm> qnm = new ArrayList<>(); while (nodes.get(0).parent != null) { QNm nm = new QNm(data.elemNames.key(nodes.get(0).name)); // skip children with prefixes if (nm.hasPrefix()) return this; for (final PathNode p : nodes) { if (nodes.get(0).name != p.name) nm = null; } qnm.add(nm); nodes = PathSummary.parent(nodes); } qc.compInfo(OPTCHILD, steps[s]); // build new steps int ts = qnm.size(); final Expr[] stps = new Expr[ts + sl - s - 1]; for (int t = 0; t < ts; t++) { final Expr[] preds = t == ts - 1 ? ((Preds) steps[s]).preds : new Expr[0]; final QNm nm = qnm.get(ts - t - 1); final NameTest nt = nm == null ? new NameTest(false) : new NameTest(nm, Kind.NAME, false, null); stps[t] = Step.get(info, CHILD, nt, preds); } while (++s < sl) stps[ts++] = steps[s]; path = get(info, root, stps); break; } // check if all steps yield results; if not, return empty sequence final ArrayList<PathNode> nodes = pathNodes(qc); if (nodes != null && nodes.isEmpty()) { qc.compInfo(OPTPATH, path); return Empty.SEQ; } return path; }
/** * 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 : funcs) { if (ls.similar(nm, lc(f.name.local()), 0)) { FUNSIMILAR.thrw(ii, name.string(), f.name.string()); } } }
@Override public String getMessage() { final TokenBuilder tb = new TokenBuilder(); if (info != null) tb.add(STOPPED_AT).add(info.toString()).add(COL).add(NL); final byte[] code = name.local(); if (code.length != 0) tb.add('[').add(name.prefixId(QueryText.ERROR_URI)).add("] "); tb.add(getLocalizedMessage()); if (!stack.isEmpty()) { tb.add(NL).add(NL).add(STACK_TRACE).add(COL); for (final InputInfo ii : stack) tb.add(NL).add(LI).add(ii.toString()); } return tb.toString(); }
/** * 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 < funcs.length; ++l) { final QNm qn = funcs[l].name; final byte[] u = qn.uri(); final byte[] nm = qn.local(); if (eq(ln, nm) && eq(uri, u) && fun.args.length == funcs[l].args.length) { // declare function that has been called before if (!funcs[l].declared) { funcs[l] = fun; return l; } // duplicate declaration FUNCDEFINED.thrw(ii, fun.name.string()); } } // add function skeleton funcs = Array.add(funcs, fun); calls = Array.add(calls, new UserFuncCall[0]); return funcs.length - 1; }
/** * Returns the specified function. * * @param name function qname * @param args optional arguments * @param ii input info * @return function instance * @throws QueryException query exception */ public StandardFunc get(final QNm name, final Expr[] args, final InputInfo ii) throws QueryException { final int id = id(name.id()); if (id == 0) return null; // create function final Function fl = funcs[id]; if (!eq(fl.uri(), name.uri())) return null; final StandardFunc f = fl.get(ii, args); // check number of arguments if (args.length < fl.min || args.length > fl.max) XPARGS.thrw(ii, fl); return f; }
/** * Throws an error if one of the pre-defined functions is similar to the specified function name. * * @param name function name * @param ii input info * @throws QueryException query exception */ public void error(final QNm name, final InputInfo ii) throws QueryException { // compare specified name with names of predefined functions final byte[] ln = name.local(); final Levenshtein ls = new Levenshtein(); for (int k = 1; k < size; ++k) { final int i = indexOf(keys[k], '}'); final byte[] u = substring(keys[k], 2, i); final byte[] l = substring(keys[k], i + 1); if (eq(ln, l)) { final byte[] ur = name.uri(); FUNSIMILAR.thrw( ii, new TokenBuilder(NSGlobal.prefix(ur)).add(':').add(l), new TokenBuilder(NSGlobal.prefix(u)).add(':').add(l)); } else if (ls.similar(ln, l, 0)) { FUNSIMILAR.thrw(ii, name.string(), l); } } }
/** * Returns a new Java function instance. * * @param name function name * @param args arguments * @param qc query context * @param sc static context * @param ii input info * @return Java function or {@code null} * @throws QueryException query exception */ static JavaMapping get( final QNm name, final Expr[] args, final QueryContext qc, final StaticContext sc, final InputInfo ii) throws QueryException { final byte[] uri = name.uri(); // check if URI starts with "java:" prefix (if yes, module must be Java code) final boolean java = startsWith(uri, JAVAPREF); // rewrite function name: convert dashes to upper-case initials final String local = camelCase(string(name.local())); // check imported Java modules final String path = camelCase(toPath(java ? substring(uri, JAVAPREF.length) : uri)); final ModuleLoader modules = qc.resources.modules(); final Object jm = modules.findImport(path); if (jm != null) { final Method meth = getModMethod(jm, path, local, args.length, qc, ii); if (meth != null) return new JavaModuleFunc(sc, ii, jm, meth, args); } // only allowed with administrator permissions if (!qc.context.user.has(Perm.ADMIN)) return null; // check addressed class try { return new JavaFunc(sc, ii, modules.findClass(path), local, args); } catch (final ClassNotFoundException ex) { // only throw exception if "java:" prefix was explicitly specified if (java) throw FUNCJAVA_X.get(ii, path); } catch (final Throwable th) { throw JAVAINIT_X.get(ii, th); } // no function found return null; }
/** * Returns a parameter. * * @param value value * @param name name * @param declared variable declaration flags * @return parameter * @throws QueryException HTTP exception */ private RestXqParam param(final Value value, final QNm name, final boolean[] declared) throws QueryException { // [CG] RESTXQ: allow identical field names? final long vs = value.size(); if (vs < 2) error(ANN_PARAMS, "%", name.string(), 2); // name of parameter final String key = toString(value.itemAt(0), name); // variable template final QNm qnm = checkVariable(toString(value.itemAt(1), name), declared); // default value final ValueBuilder vb = new ValueBuilder(); for (int v = 2; v < vs; v++) vb.add(value.itemAt(v)); return new RestXqParam(qnm, key, vb.value()); }
@Override public void compile(final QueryContext ctx) throws QueryException { if (compiled) return; compiled = true; if (check) { final StaticFunc sf = ctx.funcs.get(name, args.length, info); if (sf == null) throw Err.FUNCUNKNOWN.thrw(info, name.string()); type = sf.funcType().seqType(); } final int fp = scope.enter(ctx); try { expr = expr.compile(ctx, scope); expr.markTailCalls(); } finally { scope.cleanUp(this); scope.exit(ctx, fp); } }
/** * Returns an instance of a with the specified name and number of arguments, or {@code null}. * * @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 static TypedFunc get( final QNm name, final Expr[] args, final boolean dyn, final QueryContext ctx, final InputInfo ii) throws QueryException { // get namespace and local name // parse data type constructors if (eq(name.uri(), XSURI)) { final byte[] ln = name.local(); final AtomType type = AtomType.find(name, false); if (type == null) { final Levenshtein ls = new Levenshtein(); for (final AtomType t : AtomType.values()) { if (t.par != null && t != AtomType.NOT && t != AtomType.AAT && t != AtomType.BIN && ls.similar(lc(ln), lc(t.string()), 0)) FUNSIMILAR.thrw(ii, name.string(), t.string()); } } // no constructor function found, or abstract type specified if (type == null || type == AtomType.NOT || type == AtomType.AAT) { FUNCUNKNOWN.thrw(ii, name.string()); } if (args.length != 1) FUNCTYPE.thrw(ii, name.string()); final SeqType to = SeqType.get(type, Occ.ZERO_ONE); return TypedFunc.constr(new Cast(ii, args[0], to), to); } // pre-defined functions final StandardFunc fun = Functions.get().get(name, args, ii); if (fun != null) { if (!ctx.sc.xquery3 && fun.xquery3()) FEATURE30.thrw(ii); for (final Function f : Function.UPDATING) { if (fun.sig == f) { ctx.updating(true); break; } } return new TypedFunc(fun, fun.sig.type(args.length)); } // user-defined function final TypedFunc tf = ctx.funcs.get(name, args, ii); if (tf != null) return tf; // Java function (only allowed with administrator permissions) final JavaMapping jf = JavaMapping.get(name, args, ctx, ii); if (jf != null) return TypedFunc.java(jf); // add user-defined function that has not been declared yet if (!dyn && FuncType.find(name) == null) return ctx.funcs.add(name, args, ii, ctx); // no function found return null; }
@Override public String toString() { return new TokenBuilder(name.string()).add('#').addExt(args.length).toString(); }
/** * Checks if the location path contains steps that will never yield results. * * @param stps step array * @param ctx query context */ void voidStep(final Expr[] stps, final QueryContext ctx) { for (int l = 0; l < stps.length; ++l) { final Step s = axisStep(l); if (s == null) continue; final Axis sa = s.axis; if (l == 0) { if (root instanceof CAttr) { // @.../child:: / @.../descendant:: if (sa == CHILD || sa == DESC) { ctx.compInfo(WARNDESC, root); return; } } else if (root instanceof Root || root instanceof Value && ((Value) root).type == NodeType.DOC || root instanceof CDoc) { if (sa != CHILD && sa != DESC && sa != DESCORSELF && (sa != SELF && sa != ANCORSELF || s.test != Test.NOD && s.test != Test.DOC)) { ctx.compInfo(WARNDOC, root, sa); return; } } } else { final Step ls = axisStep(l - 1); if (ls == null) continue; final Axis lsa = ls.axis; boolean warning = true; if (sa == SELF || sa == DESCORSELF) { // .../self:: / .../descendant-or-self:: if (s.test == Test.NOD) continue; // @.../..., text()/... warning = lsa == ATTR && s.test.type != NodeType.ATT || ls.test == Test.TXT && s.test != Test.TXT; if (!warning) { if (sa == DESCORSELF) continue; // .../self:: final QNm n0 = ls.test.name; final QNm n1 = s.test.name; if (n0 == null || n1 == null || n0.local().length == 0 || n1.local().length == 0) continue; // ...X/...Y warning = !n1.eq(n0); } } else if (sa == FOLLSIBL || sa == PRECSIBL) { // .../following-sibling:: / .../preceding-sibling:: warning = lsa == ATTR; } else if (sa == DESC || sa == CHILD || sa == ATTR) { // .../descendant:: / .../child:: / .../attribute:: warning = lsa == ATTR || ls.test == Test.TXT || ls.test == Test.COM || ls.test == Test.PI || sa == ATTR && s.test == Test.NSP; } else if (sa == PARENT || sa == ANC) { // .../parent:: / .../ancestor:: warning = ls.test == Test.DOC; } if (warning) { ctx.compInfo(WARNSELF, s); return; } } } }
/** * Returns the specified value as an atomic string. * * @param value value * @param name name * @return string * @throws QueryException HTTP exception */ private String toString(final Value value, final QNm name) throws QueryException { if (!(value instanceof Str)) error(ANN_STRING, "%", name.string(), value); return ((Str) value).toJava(); }
/** * Checks a function for RESTFful annotations. * * @return {@code true} if module contains relevant annotations * @throws QueryException query exception */ boolean analyze() throws QueryException { // parse all annotations final EnumSet<HTTPMethod> mth = EnumSet.noneOf(HTTPMethod.class); final boolean[] declared = new boolean[function.args.length]; boolean found = false; final int as = function.ann.size(); for (int a = 0; a < as; a++) { final QNm name = function.ann.names[a]; final Value value = function.ann.values[a]; final byte[] local = name.local(); final byte[] uri = name.uri(); final boolean rexq = eq(uri, QueryText.RESTXQURI); if (rexq) { if (eq(PATH, local)) { // annotation "path" if (path != null) error(ANN_TWICE, "%", name.string()); path = new RestXqPath(toString(value, name)); for (final String s : path) { if (s.trim().startsWith("{")) checkVariable(s, AtomType.AAT, declared); } } else if (eq(CONSUMES, local)) { // annotation "consumes" strings(value, name, consumes); } else if (eq(PRODUCES, local)) { // annotation "produces" strings(value, name, produces); } else if (eq(QUERY_PARAM, local)) { // annotation "query-param" queryParams.add(param(value, name, declared)); } else if (eq(FORM_PARAM, local)) { // annotation "form-param" formParams.add(param(value, name, declared)); } else if (eq(HEADER_PARAM, local)) { // annotation "header-param" headerParams.add(param(value, name, declared)); } else if (eq(COOKIE_PARAM, local)) { // annotation "cookie-param" cookieParams.add(param(value, name, declared)); } else { // method annotations final HTTPMethod m = HTTPMethod.get(string(local)); if (m == null) error(ANN_UNKNOWN, "%", name.string()); if (!value.isEmpty()) { // remember post/put variable if (requestBody != null) error(ANN_TWICE, "%", name.string()); if (m != POST && m != PUT) error(METHOD_VALUE, m); requestBody = checkVariable(toString(value, name), declared); } if (mth.contains(m)) error(ANN_TWICE, "%", name.string()); mth.add(m); } } else if (eq(uri, QueryText.OUTPUTURI)) { // serialization parameters final String key = string(local); final String val = toString(value, name); if (output.get(key) == null) error(UNKNOWN_SER, key); output.set(key, val); } found |= rexq; } if (!mth.isEmpty()) methods = mth; if (found) { if (path == null) error(ANN_MISSING, PATH); for (int i = 0; i < declared.length; i++) if (!declared[i]) error(VAR_UNDEFINED, function.args[i].name.string()); } return found; }
/** * Converts descendant to child steps. * * @param ctx query context * @param data data reference * @return path */ Expr children(final QueryContext ctx, final Data data) { // skip path check if no path index exists, or if it is out-of-date if (!data.meta.uptodate || data.nspaces.globalNS() == null) return this; Path path = this; for (int s = 0; s < steps.length; ++s) { // don't allow predicates in preceding location steps final Step prev = s > 0 ? axisStep(s - 1) : null; if (prev != null && prev.preds.length != 0) break; // ignore axes other than descendant, or numeric predicates final Step curr = axisStep(s); if (curr == null || curr.axis != DESC || curr.has(Flag.FCS)) continue; // check if child steps can be retrieved for current step ArrayList<PathNode> pn = pathNodes(data, s); if (pn == null) continue; // cache child steps final ArrayList<QNm> qnm = new ArrayList<>(); while (pn.get(0).par != null) { QNm nm = new QNm(data.tagindex.key(pn.get(0).name)); // skip children with prefixes if (nm.hasPrefix()) return this; for (final PathNode p : pn) { if (pn.get(0).name != p.name) nm = null; } qnm.add(nm); pn = PathSummary.parent(pn); } ctx.compInfo(OPTCHILD, steps[s]); // build new steps int ts = qnm.size(); final Expr[] stps = new Expr[ts + steps.length - s - 1]; for (int t = 0; t < ts; ++t) { final Expr[] preds = t == ts - 1 ? ((Preds) steps[s]).preds : new Expr[0]; final QNm nm = qnm.get(ts - t - 1); final NameTest nt = nm == null ? new NameTest(false) : new NameTest(nm, Mode.LN, false, null); stps[t] = Step.get(info, CHILD, nt, preds); } while (++s < steps.length) stps[ts++] = steps[s]; path = get(info, root, stps); break; } // check if the all children in the path exist; don't test with namespaces if (data.nspaces.size() == 0) { LOOP: for (int s = 0; s < path.steps.length; ++s) { // only verify child steps; ignore namespaces final Step st = path.axisStep(s); if (st == null || st.axis != CHILD) break; if (st.test.mode == Mode.ALL || st.test.mode == null) continue; if (st.test.mode != Mode.LN) break; // check if one of the addressed nodes is on the correct level final int name = data.tagindex.id(st.test.name.local()); for (final PathNode pn : data.paths.desc(name, Data.ELEM)) { if (pn.level() == s + 1) continue LOOP; } ctx.compInfo(OPTPATH, path); return Empty.SEQ; } } return path; }
/** * Returns an index to the specified function, or {@code -1}. * * @param name name of the function * @param args optional arguments * @return function instance */ private int indexOf(final QNm name, final Expr[] args) { for (int id = 0; id < funcs.length; ++id) { if (name.eq(funcs[id].name) && args.length == funcs[id].args.length) return id; } return -1; }
/** * Checks if the specified item is a string or element. * * @param it item to be checked * @return item * @throws QueryException query exception */ private Item checkElmStr(final Item it) throws QueryException { if (it instanceof AStr || TEST.eq(it)) return it; throw ELMSTRTYPE.thrw(info, Q_ENTRY.string(), it.type); }