/** * Returns the sorted names of all available databases and, optionally, backups. Filters for * {@code name} if not {@code null} with glob support. * * @param db return databases? * @param backup return backups? * @param name name filter (may be {@code null}) * @return database and backups list */ private StringList list(final boolean db, final boolean backup, final String name) { final Pattern pt; if (name != null) { final String nm = REGEX.matcher(name).matches() ? IOFile.regex(name) : name.replaceAll("([" + REGEXCHARS + "])", "\\\\$1"); pt = Pattern.compile(nm, Prop.CASE ? 0 : Pattern.CASE_INSENSITIVE); } else { pt = null; } final IOFile[] children = soptions.dbpath().children(); final StringList list = new StringList(children.length); final HashSet<String> map = new HashSet<>(children.length); for (final IOFile f : children) { final String fn = f.name(); String add = null; if (backup && fn.endsWith(IO.ZIPSUFFIX)) { final String nn = ZIPPATTERN.split(fn)[0]; if (!nn.equals(fn)) add = nn; } else if (db && f.isDir() && fn.indexOf('.') == -1) { add = fn; } // add entry if it matches the pattern, and has not already been added if (add != null && (pt == null || pt.matcher(add).matches()) && map.add(add)) { list.add(add); } } return list.sort(false); }
/** * Chooses files that match the specified pattern. * * @param file file filter * @param content content filter * @param root root directory * @return sorted file paths * @throws InterruptedException interruption */ String[] filter(final String file, final String content, final IOFile root) throws InterruptedException { final long id = ++filterId; final TreeSet<String> results = new TreeSet<>(); final int[] search = new TokenParser(Token.lc(Token.token(content))).toArray(); // glob pattern final ProjectCache pc = cache(root); if (file.contains("*") || file.contains("?")) { final Pattern pt = Pattern.compile(IOFile.regex(file)); for (final String path : pc) { final int offset = offset(path, true); if (pt.matcher(path.substring(offset)).matches() && filterContent(path, search)) { results.add(path); if (results.size() >= MAXHITS) break; } if (id != filterId) throw new InterruptedException(); } } else { // starts-with, contains, camel case final String pttrn = file.toLowerCase(Locale.ENGLISH).replace('\\', '/'); final HashSet<String> exclude = new HashSet<>(); final boolean pathSearch = pttrn.indexOf('/') != -1; for (int i = 0; i < (pathSearch ? 2 : 3); i++) { filter(pttrn, search, i, results, exclude, pathSearch, pc, id); } } return results.toArray(new String[results.size()]); }
/** * Constructor. * @param picture variable marker (info picture) * @param def default presentation modifier * @param info input info * @throws QueryException query exception */ DateFormat(final byte[] picture, final byte[] def, final InputInfo info) throws QueryException { super(info); // split variable marker final int comma = lastIndexOf(picture, ','); byte[] pres = comma == -1 ? picture : substring(picture, 0, comma); // extract second presentation modifier final int pl = pres.length; if(pl > 1) { final int p = pres[pl - 1]; if(p == 'a' || p == 'c' || p == 'o' || p == 't') { pres = substring(pres, 0, pl - 1); if(p == 'o') ordinal = EMPTY; if(p == 't') trad = true; } } // choose first character and case finish(pres.length == 0 ? def : presentation(pres, def, true)); // check width modifier final byte[] width = comma == -1 ? null : substring(picture, comma + 1); if(width != null) { final Matcher m = WIDTH.matcher(string(width)); if(!m.find()) throw PICDATE_X.get(info, width); int i = Strings.toInt(m.group(1)); if(i != Integer.MIN_VALUE) min = i; final String mc = m.group(3); i = mc != null ? Strings.toInt(mc) : Integer.MIN_VALUE; if(i != Integer.MIN_VALUE) max = i; } }
/** * Constructor. * * @param value value * @param type item type * @param ii input info * @throws QueryException query exception */ private Dur(final byte[] value, final Type type, final InputInfo ii) throws QueryException { this(type); final String val = Token.string(value).trim(); final Matcher mt = DUR.matcher(val); if (!mt.matches() || val.endsWith("P") || val.endsWith("T")) throw dateError(value, XDURR, ii); yearMonth(value, mt, ii); dayTime(value, mt, 6, ii); }
/** * Evaluates the analyze-string function. * * @param val input value * @param ctx query context * @return function result * @throws QueryException query exception */ private Item analyzeString(final byte[] val, final QueryContext ctx) throws QueryException { final Pattern p = pattern(expr[1], expr.length == 3 ? expr[2] : null, ctx); if (p.matcher("").matches()) REGROUP.thrw(info); final String str = string(val); final Matcher m = p.matcher(str); final FElem root = new FElem(Q_ANALYZE, new Atts(FN, FNURI)); int s = 0; while (m.find()) { if (s != m.start()) nonmatch(str.substring(s, m.start()), root); match(m, str, root, 0); s = m.end(); } if (s != str.length()) nonmatch(str.substring(s), root); return root; }
/** * Evaluates the tokenize function. * * @param ctx query context * @return function result * @throws QueryException query exception */ private Value tokenize(final QueryContext ctx) throws QueryException { final byte[] val = checkEStr(expr[0], ctx); final Pattern p = pattern(expr[1], expr.length == 3 ? expr[2] : null, ctx); if (p.matcher("").matches()) REGROUP.thrw(info); final TokenList tl = new TokenList(); final String str = string(val); if (!str.isEmpty()) { final Matcher m = p.matcher(str); int s = 0; while (m.find()) { tl.add(str.substring(s, m.start())); s = m.end(); } tl.add(str.substring(s, str.length())); } return StrSeq.get(tl); }
/** * Evaluates the replace function. * * @param val input value * @param ctx query context * @return function result * @throws QueryException query exception */ private Item replace(final byte[] val, final QueryContext ctx) throws QueryException { final byte[] rep = checkStr(expr[2], ctx); for (int i = 0; i < rep.length; ++i) { if (rep[i] == '\\') { if (i + 1 == rep.length || rep[i + 1] != '\\' && rep[i + 1] != '$') FUNREPBS.thrw(info); ++i; } if (rep[i] == '$' && (i == 0 || rep[i - 1] != '\\') && (i + 1 == rep.length || !digit(rep[i + 1]))) FUNREPDOL.thrw(info); } final Pattern p = pattern(expr[1], expr.length == 4 ? expr[3] : null, ctx); if (p.pattern().isEmpty()) REGROUP.thrw(info); String r = string(rep); if ((p.flags() & Pattern.LITERAL) != 0) { r = SLASH.matcher(BSLASH.matcher(r).replaceAll("\\\\\\\\")).replaceAll("\\\\\\$"); } try { return Str.get(p.matcher(string(val)).replaceAll(r)); } catch (final Exception ex) { if (ex.getMessage().contains("No group")) REGROUP.thrw(info); throw REGPAT.thrw(info, ex); } }
/** * Initializes the date format. * * @param d input * @param e example format * @param ii input info * @throws QueryException query exception */ final void date(final byte[] d, final String e, final InputInfo ii) throws QueryException { final Matcher mt = DATE.matcher(Token.string(d).trim()); if (!mt.matches()) throw dateError(d, e, ii); yea = toLong(mt.group(1), false, ii); // +1 is added to BC values to simplify computations if (yea < 0) yea++; mon = (byte) (Strings.toInt(mt.group(3)) - 1); day = (byte) (Strings.toInt(mt.group(4)) - 1); if (mon < 0 || mon >= 12 || day < 0 || day >= dpm(yea, mon)) throw dateError(d, e, ii); if (yea <= MIN_YEAR || yea > MAX_YEAR) throw DATERANGE_X_X.get(ii, type, chop(d, ii)); zone(mt, 5, d, ii); }
/** * Handles info messages resulting from a query execution. * * @param msg info message * @return true if error was found */ private boolean error(final String msg) { final String line = msg.replaceAll("[\\r\\n].*", ""); Matcher m = XQERROR.matcher(line); int el, ec = 2; if (!m.matches()) { m = XMLERROR.matcher(line); if (!m.matches()) return true; el = Integer.parseInt(m.group(1)); errFile = getEditor().file.path(); } else { el = Integer.parseInt(m.group(1)); ec = Integer.parseInt(m.group(2)); errFile = m.group(3); } final EditorArea edit = find(IO.get(errFile), false); if (edit == null) return true; // find approximate error position final int ll = edit.last.length; int ep = ll; for (int e = 1, l = 1, c = 1; e < ll; ++c, e += cl(edit.last, e)) { if (l > el || l == el && c == ec) { ep = e; break; } if (edit.last[e] == '\n') { ++l; c = 0; } } if (ep < ll && Character.isLetterOrDigit(cp(edit.last, ep))) { while (ep > 0 && Character.isLetterOrDigit(cp(edit.last, ep - 1))) ep--; } edit.error(ep); errPos = ep; return true; }
/** * Checks the specified template and adds a variable. * * @param tmp template string * @param type allowed type * @param declared variable declaration flags * @return resulting variable * @throws QueryException query exception */ private QNm checkVariable(final String tmp, final Type type, final boolean[] declared) throws QueryException { final Var[] args = function.args; final Matcher m = TEMPLATE.matcher(tmp); if (!m.find()) error(INV_TEMPLATE, tmp); final byte[] vn = token(m.group(1)); if (!XMLToken.isQName(vn)) error(INV_VARNAME, vn); final QNm qnm = new QNm(vn, context); int r = -1; while (++r < args.length && !args[r].name.eq(qnm)) ; if (r == args.length) error(UNKNOWN_VAR, vn); if (declared[r]) error(VAR_ASSIGNED, vn); final SeqType st = args[r].declaredType(); if (args[r].checksType() && !st.type.instanceOf(type)) error(INV_VARTYPE, vn, type); declared[r] = true; return qnm; }
/** * Initializes the time format. * * @param d input format * @param e expected format * @param ii input info * @throws QueryException query exception */ final void time(final byte[] d, final String e, final InputInfo ii) throws QueryException { final Matcher mt = TIME.matcher(Token.string(d).trim()); if (!mt.matches()) throw dateError(d, e, ii); hou = (byte) Strings.toInt(mt.group(1)); min = (byte) Strings.toInt(mt.group(2)); sec = toDecimal(mt.group(3), false, ii); if (min >= 60 || sec.compareTo(BD60) >= 0 || hou > 24 || hou == 24 && (min > 0 || sec.compareTo(BigDecimal.ZERO) > 0)) throw dateError(d, e, ii); zone(mt, 5, d, ii); if (hou == 24) { hou = 0; add(DAYSECONDS); } }
/** * Parser for formatting integers in dates and times. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ final class DateFormat extends FormatParser { /** With pattern: "," min-width ("-" max-width)?. */ private static final Pattern WIDTH = Pattern.compile("^(\\*|\\d+)(-(\\*|\\d+))?$"); /** * Constructor. * * @param picture variable marker (info picture) * @param def default presentation modifier * @param info input info * @throws QueryException query exception */ DateFormat(final byte[] picture, final byte[] def, final InputInfo info) throws QueryException { super(info); // split variable marker final int comma = lastIndexOf(picture, ','); byte[] pres = comma == -1 ? picture : substring(picture, 0, comma); // extract second presentation modifier final int pl = pres.length; if (pl > 1) { final int p = pres[pl - 1]; if (p == 'a' || p == 'c' || p == 'o' || p == 't') { pres = substring(pres, 0, pl - 1); if (p == 'o') ordinal = EMPTY; if (p == 't') trad = true; } } // choose first character and case finish(pres.length == 0 ? def : presentation(pres, def, true)); // check width modifier final byte[] width = comma == -1 ? null : substring(picture, comma + 1); if (width != null) { final Matcher m = WIDTH.matcher(string(width)); if (!m.find()) throw PICDATE_X.get(info, width); int i = Strings.toInt(m.group(1)); if (i != Integer.MIN_VALUE) min = i; final String mc = m.group(3); i = mc != null ? Strings.toInt(mc) : Integer.MIN_VALUE; if (i != Integer.MIN_VALUE) max = i; } } }
/** * Duration item ({@code xs:duration}). * * @author BaseX Team 2005-16, BSD License * @author Christian Gruen */ public class Dur extends ADateDur { /** Pattern for one or more digits. */ static final String DP = "(\\d+)"; /** Date pattern. */ private static final Pattern DUR = Pattern.compile( "(-?)P(" + DP + "Y)?(" + DP + "M)?(" + DP + "D)?(T(" + DP + "H)?(" + DP + "M)?((\\d+|\\d*\\.\\d+)?S)?)?"); /** Number of months. */ long mon; /** * Constructor. * * @param value value * @param ii input info * @throws QueryException query exception */ public Dur(final byte[] value, final InputInfo ii) throws QueryException { this(value, AtomType.DUR, ii); } /** * Constructor. * * @param type item type */ Dur(final Type type) { super(type); } /** * Constructor. * * @param dur duration */ public Dur(final Dur dur) { this(dur, AtomType.DUR); } /** * Constructor. * * @param dur duration * @param type item type */ private Dur(final Dur dur, final Type type) { this(type); mon = dur.mon; sec = dur.sec == null ? BigDecimal.ZERO : dur.sec; } /** * Constructor. * * @param value value * @param type item type * @param ii input info * @throws QueryException query exception */ private Dur(final byte[] value, final Type type, final InputInfo ii) throws QueryException { this(type); final String val = Token.string(value).trim(); final Matcher mt = DUR.matcher(val); if (!mt.matches() || val.endsWith("P") || val.endsWith("T")) throw dateError(value, XDURR, ii); yearMonth(value, mt, ii); dayTime(value, mt, 6, ii); } /** * Initializes the yearMonth component. * * @param vl value * @param mt matcher * @param ii input info * @throws QueryException query exception */ void yearMonth(final byte[] vl, final Matcher mt, final InputInfo ii) throws QueryException { final long y = mt.group(2) != null ? toLong(mt.group(3), true, ii) : 0; final long m = mt.group(4) != null ? toLong(mt.group(5), true, ii) : 0; mon = y * 12 + m; double v = y * 12d + m; if (!mt.group(1).isEmpty()) { mon = -mon; v = -v; } if (v <= Long.MIN_VALUE || v >= Long.MAX_VALUE) throw DURRANGE_X_X.get(ii, type, vl); } /** * Initializes the dayTime component. * * @param vl value * @param mt matcher * @param p first matching position * @param ii input info * @throws QueryException query exception */ void dayTime(final byte[] vl, final Matcher mt, final int p, final InputInfo ii) throws QueryException { final long d = mt.group(p) != null ? toLong(mt.group(p + 1), true, ii) : 0; final long h = mt.group(p + 3) != null ? toLong(mt.group(p + 4), true, ii) : 0; final long m = mt.group(p + 5) != null ? toLong(mt.group(p + 6), true, ii) : 0; final BigDecimal s = mt.group(p + 7) != null ? toDecimal(mt.group(p + 8), true, ii) : BigDecimal.ZERO; sec = s.add(BigDecimal.valueOf(d).multiply(DAYSECONDS)) .add(BigDecimal.valueOf(h).multiply(BD3600)) .add(BigDecimal.valueOf(m).multiply(BD60)); if (!mt.group(1).isEmpty()) sec = sec.negate(); final double v = sec.doubleValue(); if (v <= Long.MIN_VALUE || v >= Long.MAX_VALUE) throw DURRANGE_X_X.get(ii, type, vl); } @Override public final long yea() { return mon / 12; } @Override public final long mon() { return mon % 12; } @Override public final long day() { return sec.divideToIntegralValue(DAYSECONDS).longValue(); } @Override public final long hou() { return tim() / 3600; } @Override public final long min() { return tim() % 3600 / 60; } @Override public final BigDecimal sec() { return sec.remainder(BD60); } /** * Returns the time. * * @return time */ private long tim() { return sec.remainder(DAYSECONDS).longValue(); } @Override public byte[] string(final InputInfo ii) { final TokenBuilder tb = new TokenBuilder(); final int ss = sec.signum(); if (mon < 0 || ss < 0) tb.add('-'); date(tb); time(tb); if (mon == 0 && ss == 0) tb.add("T0S"); return tb.finish(); } /** * Adds the date to the specified token builder. * * @param tb token builder */ final void date(final TokenBuilder tb) { tb.add('P'); final long y = yea(); if (y != 0) { tb.addLong(Math.abs(y)); tb.add('Y'); } final long m = mon(); if (m != 0) { tb.addLong(Math.abs(m)); tb.add('M'); } final long d = day(); if (d != 0) { tb.addLong(Math.abs(d)); tb.add('D'); } } /** * Adds the time to the specified token builder. * * @param tb token builder */ final void time(final TokenBuilder tb) { if (sec.remainder(DAYSECONDS).signum() == 0) return; tb.add('T'); final long h = hou(); if (h != 0) { tb.addLong(Math.abs(h)); tb.add('H'); } final long m = min(); if (m != 0) { tb.addLong(Math.abs(m)); tb.add('M'); } final BigDecimal sc = sec(); if (sc.signum() == 0) return; tb.add(Token.chopNumber(Token.token(sc.abs().toPlainString()))).add('S'); } @Override public final boolean eq( final Item it, final Collation coll, final StaticContext sc, final InputInfo ii) throws QueryException { final Dur d = (Dur) (it instanceof Dur ? it : type.cast(it, null, null, ii)); final BigDecimal s1 = sec == null ? BigDecimal.ZERO : sec; final BigDecimal s2 = d.sec == null ? BigDecimal.ZERO : d.sec; return mon == d.mon && s1.compareTo(s2) == 0; } @Override public int diff(final Item it, final Collation coll, final InputInfo ii) throws QueryException { throw diffError(ii, it, this); } @Override public final Duration toJava() { return ADate.DF.newDuration(Token.string(string(null))); } @Override public final int hash(final InputInfo ii) { return (int) (31 * mon + (sec == null ? 0 : sec.doubleValue())); } @Override public final String toString() { return Util.info("\"%\"", string(null)); } }
/** * Abstract super class for date items. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ public abstract class ADate extends ADateDur { /** Maximum value for computations on year value based on long range. */ static final long MAX_YEAR = (long) (Long.MAX_VALUE / 365.2425) - 2; /** Minimum year value. */ static final long MIN_YEAR = -MAX_YEAR; /** Constant for counting negative years (divisible by 400). */ private static final long ADD_NEG = (MAX_YEAR / 400 + 1) * 400; /** Pattern for two digits. */ static final String DD = "(\\d{2})"; /** Year pattern. */ static final String YEAR = "(-?(000[1-9]|00[1-9]\\d|0[1-9]\\d{2}|[1-9]\\d{3,}))"; /** Date pattern. */ static final String ZONE = "((\\+|-)" + DD + ':' + DD + "|Z)?"; /** Day per months. */ static final byte[] DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; /** Date pattern. */ private static final Pattern DATE = Pattern.compile(YEAR + '-' + DD + '-' + DD + ZONE); /** Time pattern. */ private static final Pattern TIME = Pattern.compile(DD + ':' + DD + ':' + "(\\d{2}(\\.\\d+)?)" + ZONE); /** * Year. * * <ul> * <li>1 - {@code Long#MAX_VALUE}-1: AD * <li>0 - {@link Long#MIN_VALUE}: BC, +1 added * <li>{@link Long#MAX_VALUE}: undefined * </ul> */ long yea = Long.MAX_VALUE; /** Month ({@code 0-11}). {@code -1}: undefined. */ byte mon = -1; /** Day ({@code 0-30}). {@code -1}: undefined. */ byte day = -1; /** Hour ({@code 0-59}). {@code -1}: undefined. */ byte hou = -1; /** Minute ({@code 0-59}). {@code -1}: undefined. */ byte min = -1; /** Timezone in minutes ({@code -14*60-14*60}). {@link Short#MAX_VALUE}: undefined. */ short tz = Short.MAX_VALUE; /** Data factory. */ static final DatatypeFactory DF; static { try { DF = DatatypeFactory.newInstance(); } catch (final Exception ex) { throw Util.notExpected(ex); } } /** * Constructor. * * @param type item type * @param date date reference */ ADate(final Type type, final ADate date) { super(type); yea = date.yea; mon = date.mon; day = date.day; hou = date.hou; min = date.min; sec = date.sec; tz = date.tz; } /** * Constructor. * * @param type item type */ ADate(final Type type) { super(type); } /** * Initializes the date format. * * @param d input * @param e example format * @param ii input info * @throws QueryException query exception */ final void date(final byte[] d, final String e, final InputInfo ii) throws QueryException { final Matcher mt = DATE.matcher(Token.string(d).trim()); if (!mt.matches()) throw dateError(d, e, ii); yea = toLong(mt.group(1), false, ii); // +1 is added to BC values to simplify computations if (yea < 0) yea++; mon = (byte) (Strings.toInt(mt.group(3)) - 1); day = (byte) (Strings.toInt(mt.group(4)) - 1); if (mon < 0 || mon >= 12 || day < 0 || day >= dpm(yea, mon)) throw dateError(d, e, ii); if (yea <= MIN_YEAR || yea > MAX_YEAR) throw DATERANGE_X_X.get(ii, type, chop(d, ii)); zone(mt, 5, d, ii); } /** * Initializes the time format. * * @param d input format * @param e expected format * @param ii input info * @throws QueryException query exception */ final void time(final byte[] d, final String e, final InputInfo ii) throws QueryException { final Matcher mt = TIME.matcher(Token.string(d).trim()); if (!mt.matches()) throw dateError(d, e, ii); hou = (byte) Strings.toInt(mt.group(1)); min = (byte) Strings.toInt(mt.group(2)); sec = toDecimal(mt.group(3), false, ii); if (min >= 60 || sec.compareTo(BD60) >= 0 || hou > 24 || hou == 24 && (min > 0 || sec.compareTo(BigDecimal.ZERO) > 0)) throw dateError(d, e, ii); zone(mt, 5, d, ii); if (hou == 24) { hou = 0; add(DAYSECONDS); } } /** * Initializes the timezone. * * @param matcher matcher * @param pos first matching position * @param value value * @param ii input info * @throws QueryException query exception */ final void zone(final Matcher matcher, final int pos, final byte[] value, final InputInfo ii) throws QueryException { final String z = matcher.group(pos); if (z == null) return; if ("Z".equals(z)) { tz = 0; } else { final int th = Strings.toInt(matcher.group(pos + 2)); final int tm = Strings.toInt(matcher.group(pos + 3)); if (th > 14 || tm > 59 || th == 14 && tm != 0) throw INVALIDZONE_X.get(ii, value); final int mn = th * 60 + tm; tz = (short) ("-".equals(matcher.group(pos + 1)) ? -mn : mn); } } /** * Adds/subtracts the specified dayTime duration. * * @param dur duration * @param plus plus/minus flag */ final void calc(final DTDur dur, final boolean plus) { add(plus ? dur.sec : dur.sec.negate()); } /** * Adds/subtracts the specified yearMonth duration. * * @param dur duration * @param plus plus/minus flag * @param ii input info * @throws QueryException query exception */ final void calc(final YMDur dur, final boolean plus, final InputInfo ii) throws QueryException { final long m = plus ? dur.mon : -dur.mon; final long mn = mon + m; mon = (byte) mod(mn, 12); yea += div(mn, 12); day = (byte) Math.min(dpm(yea, mon) - 1, day); if (yea <= MIN_YEAR || yea > MAX_YEAR) throw YEARRANGE_X.get(ii, yea); } /** * Adds the specified dayTime duration. * * @param add value to be added */ private void add(final BigDecimal add) { // normalized modulo: sc % 60 vs. (-sc + sc % 60 + 60 + sc) % 60 final BigDecimal sc = sec().add(add); sec = sc.signum() >= 0 ? sc.remainder(BD60) : sc.negate().add(sc.remainder(BD60)).add(BD60).add(sc).remainder(BD60); final long mn = Math.max(min(), 0) + div(sc.longValue(), 60); min = (byte) mod(mn, 60); final long ho = Math.max(hou, 0) + div(mn, 60); hou = (byte) mod(ho, 24); final long da = div(ho, 24); final long[] ymd = ymd(days().add(BigDecimal.valueOf(da))); yea = ymd[0]; mon = (byte) ymd[1]; day = (byte) ymd[2]; } /** * Returns a normalized module value for negative and positive values. * * @param value input value * @param mod modulo * @return result */ private static long mod(final long value, final int mod) { return value > 0 ? value % mod : (Long.MAX_VALUE / mod * mod + value) % mod; } /** * Returns a normalized division value for negative and positive values. * * @param value input value * @param div divisor * @return result */ private static long div(final long value, final int div) { return value < 0 ? (value + 1) / div - 1 : value / div; } /** * Adjusts the timezone. * * @param zone timezone * @param spec indicates if zone has been specified (may be {@code null}) * @param ii input info * @throws QueryException query exception */ public abstract void timeZone(final DTDur zone, final boolean spec, final InputInfo ii) throws QueryException; /** * Adjusts the timezone. * * @param zone timezone * @param spec indicates if zone has been specified (may be {@code null}) * @param ii input info * @throws QueryException query exception */ void tz(final DTDur zone, final boolean spec, final InputInfo ii) throws QueryException { final short t; if (spec && zone == null) { t = Short.MAX_VALUE; } else { if (zone == null) { final Calendar c = Calendar.getInstance(); t = (short) ((c.get(Calendar.ZONE_OFFSET) + c.get(Calendar.DST_OFFSET)) / 60000); } else { t = (short) (zone.min() + zone.hou() * 60); if (zone.sec().signum() != 0) throw ZONESEC_X.get(ii, zone); if (Math.abs(t) > 60 * 14 || zone.day() != 0) throw INVALZONE_X.get(ii, zone); } // change time if two competing time zones exist if (tz != Short.MAX_VALUE) add(BigDecimal.valueOf(60L * (t - tz))); } tz = t; } @Override public final long yea() { return yea > 0 ? yea : yea - 1; } @Override public final long mon() { return mon + 1; } @Override public final long day() { return day + 1; } @Override public final long hou() { return hou; } @Override public final long min() { return min; } @Override public final BigDecimal sec() { return sec == null ? BigDecimal.ZERO : sec; } /** * Returns the timezone in minutes. * * @return time zone */ public final int tz() { return tz; } /** * Returns if the timezone is defined. * * @return time zone */ public final boolean tzDefined() { return tz != Short.MAX_VALUE; } @Override public byte[] string(final InputInfo ii) { final TokenBuilder tb = new TokenBuilder(); final boolean ymd = yea != Long.MAX_VALUE; if (ymd) { if (yea <= 0) tb.add('-'); prefix(tb, Math.abs(yea()), 4); tb.add('-'); prefix(tb, mon(), 2); tb.add('-'); prefix(tb, day(), 2); } if (hou >= 0) { if (ymd) tb.add('T'); prefix(tb, hou(), 2); tb.add(':'); prefix(tb, min(), 2); tb.add(':'); if (sec.intValue() < 10) tb.add('0'); tb.addExt(Token.chopNumber(Token.token(sec().abs().toPlainString()))); } zone(tb); return tb.finish(); } /** * Adds the time zone to the specified token builder. * * @param tb token builder */ void zone(final TokenBuilder tb) { if (tz == Short.MAX_VALUE) return; if (tz == 0) { tb.add('Z'); } else { tb.add(tz > 0 ? '+' : '-'); prefix(tb, Math.abs(tz) / 60, 2); tb.add(':'); prefix(tb, Math.abs(tz) % 60, 2); } } /** * Prefixes the specified number of zero digits before a number. * * @param tb token builder * @param number number to be printed * @param zero maximum number of zero digits */ static void prefix(final TokenBuilder tb, final long number, final int zero) { final byte[] t = Token.token(number); for (int i = t.length; i < zero; i++) tb.add('0'); tb.add(t); } @Override public final boolean eq( final Item it, final Collation coll, final StaticContext sc, final InputInfo ii) throws QueryException { final ADate d = (ADate) (it instanceof ADate ? it : type.cast(it, null, null, ii)); final BigDecimal d1 = seconds().add(days().multiply(DAYSECONDS)); final BigDecimal d2 = d.seconds().add(d.days().multiply(DAYSECONDS)); return d1.compareTo(d2) == 0; } @Override public int hash(final InputInfo ii) throws QueryException { return seconds().add(days().multiply(DAYSECONDS)).intValue(); } @Override public int diff(final Item it, final Collation coll, final InputInfo ii) throws QueryException { final ADate d = (ADate) (it instanceof ADate ? it : type.cast(it, null, null, ii)); final BigDecimal d1 = seconds().add(days().multiply(DAYSECONDS)); final BigDecimal d2 = d.seconds().add(d.days().multiply(DAYSECONDS)); return d1.compareTo(d2); } @Override public final XMLGregorianCalendar toJava() { return DF.newXMLGregorianCalendar( yea == Long.MAX_VALUE ? null : BigInteger.valueOf(yea > 0 ? yea : yea - 1), mon >= 0 ? mon + 1 : Integer.MIN_VALUE, day >= 0 ? day + 1 : Integer.MIN_VALUE, hou >= 0 ? hou : Integer.MIN_VALUE, min >= 0 ? min : Integer.MIN_VALUE, sec != null ? sec.intValue() : Integer.MIN_VALUE, sec != null ? sec.remainder(BigDecimal.ONE) : null, tz == Short.MAX_VALUE ? Integer.MIN_VALUE : tz); } /** * Returns the date in seconds. * * @return seconds */ final BigDecimal seconds() { int z = tz; if (z == Short.MAX_VALUE) { // [CG] XQuery, DateTime: may be removed final long n = System.currentTimeMillis(); z = Calendar.getInstance().getTimeZone().getOffset(n) / 60000; } return (sec == null ? BigDecimal.ZERO : sec) .add(BigDecimal.valueOf(Math.max(0, hou) * 3600 + Math.max(0, min) * 60 - z * 60)); } /** * Returns a day count. * * @return days */ final BigDecimal days() { final long y = yea == Long.MAX_VALUE ? 1 : yea; return days(y + ADD_NEG, Math.max(mon, 0), Math.max(day, 0)); } /** * Returns a day count for the specified years, months and days. All values must be specified in * their internal representation (undefined values are supported, too). Algorithm is derived from * J R Stockton (http://www.merlyn.demon.co.uk/daycount.htm). * * @param year year * @param month month * @param day days * @return days */ private static BigDecimal days(final long year, final int month, final int day) { final long y = year - (month < 2 ? 1 : 0); final int m = month + (month < 2 ? 13 : 1); final int d = day + 1; return BD365 .multiply(BigDecimal.valueOf(y)) .add(BigDecimal.valueOf(y / 4 - y / 100 + y / 400 - 92 + d + (153 * m - 2) / 5)); } /** * Converts a day count into year, month and day components. Algorithm is derived from J R * Stockton (http://www.merlyn.demon.co.uk/daycount.htm). * * @param days day count * @return result array */ private static long[] ymd(final BigDecimal days) { BigDecimal d = days; BigDecimal t = d.add(BD36525).multiply(BD4).divideToIntegralValue(BD146097).subtract(BigDecimal.ONE); BigDecimal y = BD100.multiply(t); d = d.subtract(BD36524.multiply(t).add(t.divideToIntegralValue(BD4))); t = d.add(BD366).multiply(BD4).divideToIntegralValue(BD1461).subtract(BigDecimal.ONE); y = y.add(t); d = d.subtract(BD365.multiply(t).add(t.divideToIntegralValue(BD4))); final BigDecimal m = BD5.multiply(d).add(BD2).divideToIntegralValue(BD153); d = d.subtract(BD153.multiply(m).add(BD2).divideToIntegralValue(BD5)); long mm = m.longValue(); if (mm > 9) { mm -= 12; y = y.add(BigDecimal.ONE); } return new long[] {y.subtract(BigDecimal.valueOf(ADD_NEG)).longValue(), mm + 2, d.longValue()}; } /** * Returns days per month, considering leap years. * * @param yea year * @param mon month * @return days */ public static int dpm(final long yea, final int mon) { final byte l = DAYS[mon]; return mon == 1 && yea % 4 == 0 && (yea % 100 != 0 || yea % 400 == 0) ? l + 1 : l; } @Override public final String toString() { return Util.info("\"%\"", string(null)); } }
/** * Evaluates the match function. * * @param val input value * @param ctx query context * @return function result * @throws QueryException query exception */ private Item matches(final byte[] val, final QueryContext ctx) throws QueryException { final Pattern p = pattern(expr[1], expr.length == 3 ? expr[2] : null, ctx); return Bln.get(p.matcher(string(val)).find()); }
/** * This class represents a single RESTXQ function. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ final class RestXqFunction implements Comparable<RestXqFunction> { /** Pattern for a single template. */ private static final Pattern TEMPLATE = Pattern.compile("\\s*\\{\\s*\\$(.+?)\\s*\\}\\s*"); /** Supported methods. */ EnumSet<HTTPMethod> methods = EnumSet.allOf(HTTPMethod.class); /** Serialization parameters. */ final SerializerProp output = new SerializerProp(); /** Associated function. */ final StaticUserFunc function; /** Associated module. */ final RestXqModule module; /** Path. */ RestXqPath path; /** Query parameters. */ final ArrayList<RestXqParam> queryParams = new ArrayList<RestXqParam>(); /** Form parameters. */ final ArrayList<RestXqParam> formParams = new ArrayList<RestXqParam>(); /** Header parameters. */ final ArrayList<RestXqParam> headerParams = new ArrayList<RestXqParam>(); /** Cookie parameters. */ final ArrayList<RestXqParam> cookieParams = new ArrayList<RestXqParam>(); /** Query context. */ private final QueryContext context; /** Consumed media types. */ private final StringList consumes = new StringList(); /** Returned media types. */ private final StringList produces = new StringList(); /** Post/Put variable. */ private QNm requestBody; /** * Constructor. * * @param uf associated user function * @param qc query context * @param m associated module */ RestXqFunction(final StaticUserFunc uf, final QueryContext qc, final RestXqModule m) { function = uf; context = qc; module = m; } /** * Processes the HTTP request. Parses new modules and discards obsolete ones. * * @param http HTTP context * @throws Exception exception */ void process(final HTTPContext http) throws Exception { try { module.process(http, this); } catch (final QueryException ex) { if (ex.file() == null) ex.info(function.info); throw ex; } } /** * 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; } /** * Checks if an HTTP request matches this function and its constraints. * * @param http http context * @return result of check */ boolean matches(final HTTPContext http) { // check method, path, consumed and produced media type return methods.contains(http.method) && pathMatches(http) && consumes(http) && produces(http); } /** * Binds the annotated variables. * * @param http http context * @param arg argument array * @throws QueryException query exception * @throws IOException I/O exception */ void bind(final HTTPContext http, final Expr[] arg) throws QueryException, IOException { // bind variables from segments for (int s = 0; s < path.size; s++) { final Matcher m = TEMPLATE.matcher(path.segment[s]); if (!m.find()) continue; final QNm qnm = new QNm(token(m.group(1)), context); bind(qnm, arg, new Atm(http.segment(s))); } // cache request body final String ct = http.contentType(); IOContent body = null; if (requestBody != null) { body = cache(http, null); try { // bind request body in the correct format body.name(http.method + IO.XMLSUFFIX); bind(requestBody, arg, Parser.item(body, context.context.prop, ct)); } catch (final IOException ex) { error(INPUT_CONV, ex); } } // bind query parameters final Map<String, String[]> params = http.params(); for (final RestXqParam rxp : queryParams) bind(rxp, arg, params.get(rxp.key)); // bind form parameters if (!formParams.isEmpty()) { if (MimeTypes.APP_FORM.equals(ct)) { // convert parameters encoded in a form body = cache(http, body); addParams(body.toString(), params); } for (final RestXqParam rxp : formParams) bind(rxp, arg, params.get(rxp.key)); } // bind header parameters for (final RestXqParam rxp : headerParams) { final StringList sl = new StringList(); final Enumeration<?> en = http.req.getHeaders(rxp.key); while (en.hasMoreElements()) { for (final String s : en.nextElement().toString().split(", *")) sl.add(s); } bind(rxp, arg, sl.toArray()); } // bind cookie parameters final Cookie[] ck = http.req.getCookies(); for (final RestXqParam rxp : cookieParams) { String v = null; if (ck != null) { for (final Cookie c : ck) { if (rxp.key.equals(c.getName())) v = c.getValue(); } } if (v == null) bind(rxp, arg); else bind(rxp, arg, v); } } /** * Creates an exception with the specified message. * * @param msg message * @param ext error extension * @return exception * @throws QueryException query exception */ QueryException error(final String msg, final Object... ext) throws QueryException { throw new QueryException(function.info, Err.BASX_RESTXQ, Util.info(msg, ext)); } @Override public int compareTo(final RestXqFunction rxf) { return path.compareTo(rxf.path); } // PRIVATE METHODS ==================================================================== /** * Checks the specified template and adds a variable. * * @param tmp template string * @param declared variable declaration flags * @return resulting variable * @throws QueryException query exception */ private QNm checkVariable(final String tmp, final boolean[] declared) throws QueryException { return checkVariable(tmp, AtomType.ITEM, declared); } /** * Checks the specified template and adds a variable. * * @param tmp template string * @param type allowed type * @param declared variable declaration flags * @return resulting variable * @throws QueryException query exception */ private QNm checkVariable(final String tmp, final Type type, final boolean[] declared) throws QueryException { final Var[] args = function.args; final Matcher m = TEMPLATE.matcher(tmp); if (!m.find()) error(INV_TEMPLATE, tmp); final byte[] vn = token(m.group(1)); if (!XMLToken.isQName(vn)) error(INV_VARNAME, vn); final QNm qnm = new QNm(vn, context); int r = -1; while (++r < args.length && !args[r].name.eq(qnm)) ; if (r == args.length) error(UNKNOWN_VAR, vn); if (declared[r]) error(VAR_ASSIGNED, vn); final SeqType st = args[r].declaredType(); if (args[r].checksType() && !st.type.instanceOf(type)) error(INV_VARTYPE, vn, type); declared[r] = true; return qnm; } /** * Checks if the path matches the HTTP request. * * @param http http context * @return result of check */ private boolean pathMatches(final HTTPContext http) { return path.matches(http); } /** * Checks if the consumed content type matches. * * @param http http context * @return result of check */ private boolean consumes(final HTTPContext http) { // return true if no type is given if (consumes.isEmpty()) return true; // return true if no content type is specified by the user final String ct = http.contentType(); if (ct == null) return true; // check if any combination matches for (final String c : consumes) { if (MimeTypes.matches(c, ct)) return true; } return false; } /** * Checks if the produced content type matches. * * @param http http context * @return result of check */ private boolean produces(final HTTPContext http) { // return true if no type is given if (produces.isEmpty()) return true; // check if any combination matches for (final String pr : http.produces()) { for (final String p : produces) { if (MimeTypes.matches(p, pr)) return true; } } return false; } /** * Binds the specified parameter to a variable. * * @param rxp parameter * @param args argument array * @param values values to be bound; the parameter's default value is assigned if the argument is * {@code null} or empty * @throws QueryException query exception */ private void bind(final RestXqParam rxp, final Expr[] args, final String... values) throws QueryException { final Value val; if (values == null || values.length == 0) { val = rxp.value; } else { final ValueBuilder vb = new ValueBuilder(); for (final String s : values) vb.add(new Atm(s)); val = vb.value(); } bind(rxp.name, args, val); } /** * Binds the specified value to a variable. * * @param name variable name * @param args argument array * @param value value to be bound * @throws QueryException query exception */ private void bind(final QNm name, final Expr[] args, final Value value) throws QueryException { // skip nulled values if (value == null) return; for (int i = 0; i < function.args.length; i++) { final Var var = function.args[i]; if (!var.name.eq(name)) continue; // casts and binds the value args[i] = var.checkType(value, context, null); break; } } /** * 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(); } /** * Adds items to the specified list. * * @param value value * @param name name * @param list list to add values to * @throws QueryException HTTP exception */ private void strings(final Value value, final QNm name, final StringList list) throws QueryException { final long vs = value.size(); for (int v = 0; v < vs; v++) list.add(toString(value.itemAt(v), name)); } /** * 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()); } // PRIVATE STATIC METHODS ============================================================= /** * Caches the request body, if not done yet. * * @param http http context * @param cache cache existing cache reference * @return cache * @throws IOException I/O exception */ private static IOContent cache(final HTTPContext http, final IOContent cache) throws IOException { if (cache != null) return cache; final BufferInput bi = new BufferInput(http.req.getInputStream()); final IOContent io = new IOContent(bi.content()); io.name(http.method + IO.XMLSUFFIX); return io; } /** * Adds parameters from the passed on request body. * * @param body request body * @param params map parameters */ private static void addParams(final String body, final Map<String, String[]> params) { for (final String nv : body.split("&")) { final String[] parts = nv.split("=", 2); if (parts.length < 2) continue; try { params.put(parts[0], new String[] {URLDecoder.decode(parts[1], Token.UTF8)}); } catch (final Exception ex) { Util.notexpected(ex); } } } }
/** * String pattern functions. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class FNPat extends StandardFunc { /** Pattern cache. */ private final TokenObjMap<Pattern> patterns = new TokenObjMap<Pattern>(); /** Slash pattern. */ private static final Pattern SLASH = Pattern.compile("\\$"); /** Slash pattern. */ private static final Pattern BSLASH = Pattern.compile("\\\\"); /** Root element for the analyze-string-result function. */ private static final QNm Q_ANALYZE = new QNm("fn:analyze-string-result", FNURI); /** Element for the analyze-string-result function. */ private static final QNm Q_MATCH = new QNm("fn:match", FNURI); /** Element for the analyze-string-result function. */ private static final QNm Q_NONMATCH = new QNm("fn:non-match", FNURI); /** Element for the analyze-string-result function. */ private static final QNm Q_MGROUP = new QNm("fn:group", FNURI); /** Attribute for the analyze-string-result function. */ private static final QNm Q_NR = new QNm("nr"); /** * Constructor. * * @param ii input info * @param f function definition * @param e arguments */ public FNPat(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 TOKENIZE: return tokenize(ctx).iter(); default: return super.iter(ctx); } } @Override public Value value(final QueryContext ctx) throws QueryException { switch (sig) { case TOKENIZE: return tokenize(ctx); default: return super.value(ctx); } } @Override public Item item(final QueryContext ctx, final InputInfo ii) throws QueryException { switch (sig) { case MATCHES: return matches(checkEStr(expr[0], ctx), ctx); case REPLACE: return replace(checkEStr(expr[0], ctx), ctx); case ANALYZE_STRING: return analyzeString(checkEStr(expr[0], ctx), ctx); default: return super.item(ctx, ii); } } /** * Evaluates the match function. * * @param val input value * @param ctx query context * @return function result * @throws QueryException query exception */ private Item matches(final byte[] val, final QueryContext ctx) throws QueryException { final Pattern p = pattern(expr[1], expr.length == 3 ? expr[2] : null, ctx); return Bln.get(p.matcher(string(val)).find()); } /** * Evaluates the analyze-string function. * * @param val input value * @param ctx query context * @return function result * @throws QueryException query exception */ private Item analyzeString(final byte[] val, final QueryContext ctx) throws QueryException { final Pattern p = pattern(expr[1], expr.length == 3 ? expr[2] : null, ctx); if (p.matcher("").matches()) REGROUP.thrw(info); final String str = string(val); final Matcher m = p.matcher(str); final FElem root = new FElem(Q_ANALYZE, new Atts(FN, FNURI)); int s = 0; while (m.find()) { if (s != m.start()) nonmatch(str.substring(s, m.start()), root); match(m, str, root, 0); s = m.end(); } if (s != str.length()) nonmatch(str.substring(s), root); return root; } /** * Processes a match. * * @param m matcher * @param str string * @param par parent * @param g group number * @return next group number and position in string */ private static int[] match(final Matcher m, final String str, final FElem par, final int g) { final FElem nd = new FElem(g == 0 ? Q_MATCH : Q_MGROUP, new Atts(FN, FNURI)); if (g > 0) nd.add(Q_NR, token(g)); final int start = m.start(g), end = m.end(g), gc = m.groupCount(); int[] pos = {g + 1, start}; // group and position in string while (pos[0] <= gc && m.end(pos[0]) <= end) { final int st = m.start(pos[0]); if (st >= 0) { // group matched if (pos[1] < st) nd.add(str.substring(pos[1], st)); pos = match(m, str, nd, pos[0]); } else pos[0]++; // skip it } if (pos[1] < end) { nd.add(str.substring(pos[1], end)); pos[1] = end; } par.add(nd); return pos; } /** * Processes a non-match. * * @param text text * @param par root node */ private static void nonmatch(final String text, final FElem par) { par.add(new FElem(Q_NONMATCH, new Atts(FN, FNURI)).add(text)); } /** * Evaluates the replace function. * * @param val input value * @param ctx query context * @return function result * @throws QueryException query exception */ private Item replace(final byte[] val, final QueryContext ctx) throws QueryException { final byte[] rep = checkStr(expr[2], ctx); for (int i = 0; i < rep.length; ++i) { if (rep[i] == '\\') { if (i + 1 == rep.length || rep[i + 1] != '\\' && rep[i + 1] != '$') FUNREPBS.thrw(info); ++i; } if (rep[i] == '$' && (i == 0 || rep[i - 1] != '\\') && (i + 1 == rep.length || !digit(rep[i + 1]))) FUNREPDOL.thrw(info); } final Pattern p = pattern(expr[1], expr.length == 4 ? expr[3] : null, ctx); if (p.pattern().isEmpty()) REGROUP.thrw(info); String r = string(rep); if ((p.flags() & Pattern.LITERAL) != 0) { r = SLASH.matcher(BSLASH.matcher(r).replaceAll("\\\\\\\\")).replaceAll("\\\\\\$"); } try { return Str.get(p.matcher(string(val)).replaceAll(r)); } catch (final Exception ex) { if (ex.getMessage().contains("No group")) REGROUP.thrw(info); throw REGPAT.thrw(info, ex); } } /** * Evaluates the tokenize function. * * @param ctx query context * @return function result * @throws QueryException query exception */ private Value tokenize(final QueryContext ctx) throws QueryException { final byte[] val = checkEStr(expr[0], ctx); final Pattern p = pattern(expr[1], expr.length == 3 ? expr[2] : null, ctx); if (p.matcher("").matches()) REGROUP.thrw(info); final TokenList tl = new TokenList(); final String str = string(val); if (!str.isEmpty()) { final Matcher m = p.matcher(str); int s = 0; while (m.find()) { tl.add(str.substring(s, m.start())); s = m.end(); } tl.add(str.substring(s, str.length())); } return StrSeq.get(tl); } /** * Returns a regular expression pattern. * * @param pattern input pattern * @param modifier modifier item * @param ctx query context * @return pattern modifier * @throws QueryException query exception */ private Pattern pattern(final Expr pattern, final Expr modifier, final QueryContext ctx) throws QueryException { final byte[] pat = checkStr(pattern, ctx); final byte[] mod = modifier != null ? checkStr(modifier, ctx) : null; final TokenBuilder tb = new TokenBuilder(pat); if (mod != null) tb.add(0).add(mod); final byte[] key = tb.finish(); Pattern p = patterns.get(key); if (p == null) { p = RegExParser.parse(pat, mod, ctx.sc.xquery3(), info); patterns.add(key, p); } return p; } @Override public boolean xquery3() { return sig == ANALYZE_STRING; } @Override public boolean uses(final Use u) { return u == Use.X30 && xquery3() || u == Use.CNS && sig == ANALYZE_STRING || super.uses(u); } }
/** * This view allows the input and evaluation of queries and documents. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class EditorView extends View { /** Error string. */ private static final String ERRSTRING = STOPPED_AT + ' ' + (LINE_X + ", " + COLUMN_X).replaceAll("%", "([0-9]+)"); /** XQuery error pattern. */ private static final Pattern XQERROR = Pattern.compile(ERRSTRING + ' ' + IN_FILE_X.replaceAll("%", "(.*?)") + COL); /** XML error pattern. */ private static final Pattern XMLERROR = Pattern.compile(LINE_X.replaceAll("%", "(.*?)") + COL + ".*"); /** History Button. */ final BaseXButton hist; /** Execute Button. */ final BaseXButton stop; /** Info label. */ final BaseXLabel info; /** Position label. */ final BaseXLabel pos; /** Query area. */ final BaseXTabs tabs; /** Execute button. */ final BaseXButton go; /** Thread counter. */ int threadID; /** File in which the most recent error occurred. */ String errFile; /** Most recent error position; used for clicking on error message. */ int errPos; /** Header string. */ private final BaseXLabel header; /** Filter button. */ private final BaseXButton filter; /** Search panel. */ public final SearchPanel search; /** * Default constructor. * * @param man view manager */ public EditorView(final ViewNotifier man) { super(EDITORVIEW, man); if (Prop.langright) applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); border(6, 6, 6, 6).layout(new BorderLayout(0, 2)).setFocusable(false); header = new BaseXLabel(EDITOR, true, false); final BaseXButton srch = new BaseXButton(gui, "search", H_REPLACE); final BaseXButton openB = BaseXButton.command(GUICommands.C_EDITOPEN, gui); final BaseXButton saveB = new BaseXButton(gui, "save", H_SAVE); hist = new BaseXButton(gui, "hist", H_RECENTLY_OPEN); final BaseXBack buttons = new BaseXBack(Fill.NONE); buttons.layout(new TableLayout(1, 4, 1, 0)); buttons.add(srch); buttons.add(openB); buttons.add(saveB); buttons.add(hist); final BaseXBack b = new BaseXBack(Fill.NONE).layout(new BorderLayout(8, 0)); if (Prop.langright) { b.add(header, BorderLayout.EAST); b.add(buttons, BorderLayout.WEST); } else { b.add(header, BorderLayout.CENTER); b.add(buttons, BorderLayout.EAST); } add(b, BorderLayout.NORTH); tabs = new BaseXTabs(gui); tabs.setFocusable(false); final SearchEditor se = new SearchEditor(gui, tabs, null).button(srch); search = se.panel(); addCreateTab(); add(se, BorderLayout.CENTER); // status and query pane search.editor(addTab(), false); info = new BaseXLabel().setText(OK, Msg.SUCCESS); pos = new BaseXLabel(" "); posCode.invokeLater(); stop = new BaseXButton(gui, "stop", H_STOP_PROCESS); stop.addKeyListener(this); stop.setEnabled(false); go = new BaseXButton(gui, "go", H_EXECUTE_QUERY); go.addKeyListener(this); filter = BaseXButton.command(GUICommands.C_FILTER, gui); filter.addKeyListener(this); filter.setEnabled(false); final BaseXBack status = new BaseXBack(Fill.NONE).layout(new BorderLayout(4, 0)); status.add(info, BorderLayout.CENTER); status.add(pos, BorderLayout.EAST); final BaseXBack query = new BaseXBack(Fill.NONE).layout(new TableLayout(1, 3, 1, 0)); query.add(stop); query.add(go); query.add(filter); final BaseXBack south = new BaseXBack(Fill.NONE).border(4, 0, 0, 0); south.layout(new BorderLayout(8, 0)); south.add(status, BorderLayout.CENTER); south.add(query, BorderLayout.EAST); add(south, BorderLayout.SOUTH); refreshLayout(); // add listeners saveB.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { final JPopupMenu pop = new JPopupMenu(); final StringBuilder mnem = new StringBuilder(); final JMenuItem sa = GUIMenu.newItem(GUICommands.C_EDITSAVE, gui, mnem); final JMenuItem sas = GUIMenu.newItem(GUICommands.C_EDITSAVEAS, gui, mnem); GUICommands.C_EDITSAVE.refresh(gui, sa); GUICommands.C_EDITSAVEAS.refresh(gui, sas); pop.add(sa); pop.add(sas); pop.show(saveB, 0, saveB.getHeight()); } }); hist.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { final JPopupMenu pm = new JPopupMenu(); final ActionListener al = new ActionListener() { @Override public void actionPerformed(final ActionEvent ac) { open(new IOFile(ac.getActionCommand())); } }; final StringList sl = new StringList(); for (final EditorArea ea : editors()) sl.add(ea.file.path()); for (final String en : new StringList().add(gui.gprop.strings(GUIProp.EDITOR)).sort(!Prop.WIN, true)) { final JMenuItem it = new JMenuItem(en); it.setEnabled(!sl.contains(en)); pm.add(it).addActionListener(al); } pm.show(hist, 0, hist.getHeight()); } }); refreshHistory(null); info.addMouseListener( new MouseAdapter() { @Override public void mouseClicked(final MouseEvent e) { EditorArea ea = getEditor(); if (errFile != null) { ea = find(IO.get(errFile), false); if (ea == null) ea = open(new IOFile(errFile)); tabs.setSelectedComponent(ea); } if (errPos == -1) return; ea.jumpError(errPos); posCode.invokeLater(); } }); stop.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { stop.setEnabled(false); go.setEnabled(false); gui.stop(); } }); go.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { getEditor().release(Action.EXECUTE); } }); tabs.addChangeListener( new ChangeListener() { @Override public void stateChanged(final ChangeEvent e) { final EditorArea ea = getEditor(); if (ea == null) return; search.editor(ea, true); gui.refreshControls(); posCode.invokeLater(); } }); BaseXLayout.addDrop( this, new DropHandler() { @Override public void drop(final Object file) { if (file instanceof File) open(new IOFile((File) file)); } }); } @Override public void refreshInit() {} @Override public void refreshFocus() {} @Override public void refreshMark() { final EditorArea edit = getEditor(); go.setEnabled(edit.script || edit.xquery && !gui.gprop.is(GUIProp.EXECRT)); final Nodes mrk = gui.context.marked; filter.setEnabled(!gui.gprop.is(GUIProp.FILTERRT) && mrk != null && mrk.size() != 0); } @Override public void refreshContext(final boolean more, final boolean quick) {} @Override public void refreshLayout() { header.setFont(GUIConstants.lfont); for (final EditorArea edit : editors()) edit.setFont(GUIConstants.mfont); search.refreshLayout(); } @Override public void refreshUpdate() {} @Override public boolean visible() { return gui.gprop.is(GUIProp.SHOWEDITOR); } @Override public void visible(final boolean v) { gui.gprop.set(GUIProp.SHOWEDITOR, v); } @Override protected boolean db() { return false; } /** Opens a new file. */ public void open() { // open file chooser for XML creation final BaseXFileChooser fc = new BaseXFileChooser(OPEN, gui.gprop.get(GUIProp.WORKPATH), gui); fc.filter(BXS_FILES, IO.BXSSUFFIX); fc.filter(XQUERY_FILES, IO.XQSUFFIXES); fc.filter(XML_DOCUMENTS, IO.XMLSUFFIXES); final IOFile[] files = fc.multi().selectAll(Mode.FOPEN); for (final IOFile f : files) open(f); } /** Reverts the contents of the currently opened editor. */ public void reopen() { getEditor().reopen(true); } /** * Saves the contents of the currently opened editor. * * @return {@code false} if operation was canceled */ public boolean save() { final EditorArea edit = getEditor(); return edit.opened() ? save(edit.file) : saveAs(); } /** * Saves the contents of the currently opened editor under a new name. * * @return {@code false} if operation was canceled */ public boolean saveAs() { // open file chooser for XML creation final EditorArea edit = getEditor(); final BaseXFileChooser fc = new BaseXFileChooser(SAVE_AS, edit.file.path(), gui).filter(XQUERY_FILES, IO.XQSUFFIXES); final IOFile file = fc.select(Mode.FSAVE); return file != null && save(file); } /** Creates a new file. */ public void newFile() { addTab(); refreshControls(true); } /** * Opens the specified query file. * * @param file query file * @return opened editor */ public EditorArea open(final IOFile file) { if (!visible()) GUICommands.C_SHOWEDITOR.execute(gui); EditorArea edit = find(file, true); try { if (edit != null) { // display open file tabs.setSelectedComponent(edit); edit.reopen(true); } else { // get current editor edit = getEditor(); // create new tab if current text is stored on disk or has been modified if (edit.opened() || edit.modified) edit = addTab(); edit.initText(file.read()); edit.file(file); } } catch (final IOException ex) { BaseXDialog.error(gui, FILE_NOT_OPENED); } return edit; } /** * Refreshes the list of recent query files and updates the query path. * * @param file new file */ void refreshHistory(final IOFile file) { final StringList sl = new StringList(); String path = null; if (file != null) { path = file.path(); gui.gprop.set(GUIProp.WORKPATH, file.dirPath()); sl.add(path); tabs.setToolTipTextAt(tabs.getSelectedIndex(), path); } final String[] qu = gui.gprop.strings(GUIProp.EDITOR); for (int q = 0; q < qu.length && q < 19; q++) { final String f = qu[q]; if (!f.equalsIgnoreCase(path) && IO.get(f).exists()) sl.add(f); } // store sorted history gui.gprop.set(GUIProp.EDITOR, sl.toArray()); hist.setEnabled(!sl.isEmpty()); } /** * Closes an editor. * * @param edit editor to be closed. {@code null} closes the currently opened editor. * @return {@code true} if editor was closed */ public boolean close(final EditorArea edit) { final EditorArea ea = edit != null ? edit : getEditor(); if (!confirm(ea)) return false; tabs.remove(ea); final int t = tabs.getTabCount(); final int i = tabs.getSelectedIndex(); if (t == 1) { // reopen single tab addTab(); } else if (i + 1 == t) { // if necessary, activate last editor tab tabs.setSelectedIndex(i - 1); } return true; } /** Jumps to a specific line. */ public void gotoLine() { final EditorArea edit = getEditor(); final int ll = edit.last.length; final int cr = edit.getCaret(); int l = 1; for (int e = 0; e < ll && e < cr; e += cl(edit.last, e)) { if (edit.last[e] == '\n') ++l; } final DialogLine dl = new DialogLine(gui, l); if (!dl.ok()) return; final int el = dl.line(); int p = 0; l = 1; for (int e = 0; e < ll && l < el; e += cl(edit.last, e)) { if (edit.last[e] != '\n') continue; p = e + 1; ++l; } edit.setCaret(p); posCode.invokeLater(); } /** Starts a thread, which shows a waiting info after a short timeout. */ public void start() { final int thread = threadID; new Thread() { @Override public void run() { Performance.sleep(200); if (thread == threadID) { info.setText(PLEASE_WAIT_D, Msg.SUCCESS).setToolTipText(null); stop.setEnabled(true); } } }.start(); } /** * Evaluates the info message resulting from a parsed or executed query. * * @param msg info message * @param ok {@code true} if evaluation was successful * @param up update */ public void info(final String msg, final boolean ok, final boolean up) { ++threadID; errPos = -1; errFile = null; getEditor().resetError(); final String m = msg.replaceAll("^.*\r?\n\\[.*?\\]", "") .replaceAll(".*" + LINE_X.replaceAll("%", ".*?") + COL, ""); if (ok) { info.setCursor(GUIConstants.CURSORARROW); info.setText(m, Msg.SUCCESS).setToolTipText(null); } else { info.setCursor(error(msg) ? GUIConstants.CURSORHAND : GUIConstants.CURSORARROW); info.setText(m, Msg.ERROR).setToolTipText(msg); } if (up) { stop.setEnabled(false); refreshMark(); } } /** * Handles info messages resulting from a query execution. * * @param msg info message * @return true if error was found */ private boolean error(final String msg) { final String line = msg.replaceAll("[\\r\\n].*", ""); Matcher m = XQERROR.matcher(line); int el, ec = 2; if (!m.matches()) { m = XMLERROR.matcher(line); if (!m.matches()) return true; el = Integer.parseInt(m.group(1)); errFile = getEditor().file.path(); } else { el = Integer.parseInt(m.group(1)); ec = Integer.parseInt(m.group(2)); errFile = m.group(3); } final EditorArea edit = find(IO.get(errFile), false); if (edit == null) return true; // find approximate error position final int ll = edit.last.length; int ep = ll; for (int e = 1, l = 1, c = 1; e < ll; ++c, e += cl(edit.last, e)) { if (l > el || l == el && c == ec) { ep = e; break; } if (edit.last[e] == '\n') { ++l; c = 0; } } if (ep < ll && Character.isLetterOrDigit(cp(edit.last, ep))) { while (ep > 0 && Character.isLetterOrDigit(cp(edit.last, ep - 1))) ep--; } edit.error(ep); errPos = ep; return true; } /** * Shows a quit dialog for all modified query files. * * @return {@code false} if confirmation was canceled */ public boolean confirm() { for (final EditorArea edit : editors()) { tabs.setSelectedComponent(edit); if (!close(edit)) return false; } return true; } /** * Checks if the current text can be saved or reverted. * * @param rev revert flag * @return result of check */ public boolean modified(final boolean rev) { final EditorArea edit = getEditor(); return edit.modified || !rev && !edit.opened(); } /** * Returns the current editor. * * @return editor */ public EditorArea getEditor() { final Component c = tabs.getSelectedComponent(); return c instanceof EditorArea ? (EditorArea) c : null; } /** * Refreshes the query modification flag. * * @param force action */ void refreshControls(final boolean force) { // update modification flag final EditorArea edit = getEditor(); final boolean oe = edit.modified; edit.modified = edit.hist != null && edit.hist.modified(); if (edit.modified == oe && !force) return; // update tab title String title = edit.file.name(); if (edit.modified) title += '*'; edit.label.setText(title); // update components gui.refreshControls(); posCode.invokeLater(); } /** Code for setting cursor position. */ final GUICode posCode = new GUICode() { @Override public void eval(final Object arg) { final int[] lc = getEditor().pos(); pos.setText(lc[0] + " : " + lc[1]); } }; /** * Finds the editor that contains the specified file. * * @param file file to be found * @param opened considers only opened files * @return editor */ EditorArea find(final IO file, final boolean opened) { for (final EditorArea edit : editors()) { if (edit.file.eq(file) && (!opened || edit.opened())) return edit; } return null; } /** * Saves the specified editor contents. * * @param file file to write * @return {@code false} if confirmation was canceled */ private boolean save(final IOFile file) { try { final EditorArea edit = getEditor(); file.write(edit.getText()); edit.file(file); return true; } catch (final IOException ex) { BaseXDialog.error(gui, FILE_NOT_SAVED); return false; } } /** * Choose a unique tab file. * * @return io reference */ private IOFile newTabFile() { // collect numbers of existing files final BoolList bl = new BoolList(); for (final EditorArea edit : editors()) { if (edit.opened()) continue; final String n = edit.file.name().substring(FILE.length()); bl.set(n.isEmpty() ? 1 : Integer.parseInt(n), true); } // find first free file number int c = 0; while (++c < bl.size() && bl.get(c)) ; // create io reference return new IOFile(gui.gprop.get(GUIProp.WORKPATH), FILE + (c == 1 ? "" : c)); } /** * Adds a new editor tab. * * @return editor reference */ EditorArea addTab() { final EditorArea edit = new EditorArea(this, newTabFile()); edit.setFont(GUIConstants.mfont); final BaseXBack tab = new BaseXBack(new BorderLayout(10, 0)).mode(Fill.NONE); tab.add(edit.label, BorderLayout.CENTER); final BaseXButton close = tabButton("e_close"); close.setRolloverIcon(BaseXLayout.icon("e_close2")); close.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { close(edit); } }); tab.add(close, BorderLayout.EAST); tabs.add(edit, tab, tabs.getComponentCount() - 2); return edit; } /** Adds a tab for creating new tabs. */ private void addCreateTab() { final BaseXButton add = tabButton("e_new"); add.setRolloverIcon(BaseXLayout.icon("e_new2")); add.addActionListener( new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { addTab(); refreshControls(true); } }); tabs.add(new BaseXBack(), add, 0); tabs.setEnabledAt(0, false); } /** * Adds a new tab button. * * @param icon button icon * @return button */ private BaseXButton tabButton(final String icon) { final BaseXButton b = new BaseXButton(gui, icon, null); b.border(2, 2, 2, 2).setContentAreaFilled(false); b.setFocusable(false); return b; } /** * Shows a quit dialog for the specified editor. * * @param edit editor to be saved * @return {@code false} if confirmation was canceled */ private boolean confirm(final EditorArea edit) { if (edit.modified && (edit.opened() || edit.getText().length != 0)) { final Boolean ok = BaseXDialog.yesNoCancel(gui, Util.info(CLOSE_FILE_X, edit.file.name())); if (ok == null || ok && !save()) return false; } return true; } /** * Returns all editors. * * @return editors */ EditorArea[] editors() { final ArrayList<EditorArea> edits = new ArrayList<EditorArea>(); for (final Component c : tabs.getComponents()) { if (c instanceof EditorArea) edits.add((EditorArea) c); } return edits.toArray(new EditorArea[edits.size()]); } }
/** * Provides central access to all databases and backups. * * @author BaseX Team 2005-14, BSD License * @author Jens Erat */ public final class Databases { /** * Allowed characters for database names (additional to letters and digits). The following * characters are invalid: * * <ul> * <li>{@code ,?*}" are used by the glob syntax * <li>{@code ;} is reserved for separating commands. * <li>{@code :*?\"<>\/|}" are used for filenames and paths * </ul> */ static final String DBCHARS = "-+=~!#$%^&()[]{}@'`"; /** Regex representation of allowed database characters. */ public static final String REGEXCHARS = DBCHARS.replaceAll("(.)", "\\\\$1"); /** Pattern to extract the database name from a backup file name. */ private static final Pattern ZIPPATTERN = Pattern.compile(DateTime.PATTERN + '\\' + IO.ZIPSUFFIX + '$'); /** Regex indicator. */ private static final Pattern REGEX = Pattern.compile(".*[*?,].*"); /** Static options. */ private final StaticOptions soptions; /** * Creates a new instance and loads available databases. * * @param soptions static options */ Databases(final StaticOptions soptions) { this.soptions = soptions; } /** * Lists all available databases and backups. * * @return database and backup list */ public StringList list() { return list(true, true, null); } /** * Lists all available databases. * * @return database list */ public StringList listDBs() { return list(true, false, null); } /** * Lists all available databases matching the given name. Supports glob patterns. * * @param name database name, glob patterns allowed * @return database list */ public StringList listDBs(final String name) { return list(true, false, name); } /** * Returns the sorted names of all available databases and, optionally, backups. Filters for * {@code name} if not {@code null} with glob support. * * @param db return databases? * @param backup return backups? * @param name name filter (may be {@code null}) * @return database and backups list */ private StringList list(final boolean db, final boolean backup, final String name) { final Pattern pt; if (name != null) { final String nm = REGEX.matcher(name).matches() ? IOFile.regex(name) : name.replaceAll("([" + REGEXCHARS + "])", "\\\\$1"); pt = Pattern.compile(nm, Prop.CASE ? 0 : Pattern.CASE_INSENSITIVE); } else { pt = null; } final IOFile[] children = soptions.dbpath().children(); final StringList list = new StringList(children.length); final HashSet<String> map = new HashSet<>(children.length); for (final IOFile f : children) { final String fn = f.name(); String add = null; if (backup && fn.endsWith(IO.ZIPSUFFIX)) { final String nn = ZIPPATTERN.split(fn)[0]; if (!nn.equals(fn)) add = nn; } else if (db && f.isDir() && fn.indexOf('.') == -1) { add = fn; } // add entry if it matches the pattern, and has not already been added if (add != null && (pt == null || pt.matcher(add).matches()) && map.add(add)) { list.add(add); } } return list.sort(false); } /** * Returns the names of all backups. * * @return backups */ public StringList backups() { final StringList backups = new StringList(); for (final IOFile f : soptions.dbpath().children()) { final String n = f.name(); if (n.endsWith(IO.ZIPSUFFIX)) backups.add(n.substring(0, n.lastIndexOf('.'))); } return backups; } /** * Returns the name of a specific backup, or all backups found for a specific database, in a * descending order. * * @param db database * @return names of specified backups */ public StringList backups(final String db) { final StringList backups = new StringList(); final IOFile file = soptions.dbpath(db + IO.ZIPSUFFIX); if (file.exists()) { backups.add(db); } else { final String regex = db.replaceAll("([" + REGEXCHARS + "])", "\\\\$1") + DateTime.PATTERN + IO.ZIPSUFFIX; for (final IOFile f : soptions.dbpath().children()) { final String n = f.name(); if (n.matches(regex)) backups.add(n.substring(0, n.lastIndexOf('.'))); } } return backups.sort(Prop.CASE, false); } /** * Extracts the name of a database from the name of a backup. * * @param backup Name of the backup file. Valid formats: {@code [dbname]-yyyy-mm-dd-hh-mm-ss}, * {@code [dbname]} * @return name of the database ({@code [dbname]}) */ public static String name(final String backup) { return Pattern.compile(DateTime.PATTERN + '$').split(backup)[0]; } /** * Checks if the specified character is a valid character for a database name. * * @param ch the character to be checked * @return result of check */ public static boolean validChar(final int ch) { return Token.letterOrDigit(ch) || DBCHARS.indexOf(ch) != -1; } /** * Checks if the specified string is a valid database name. * * @param name name to be checked * @return result of check */ public static boolean validName(final String name) { return validName(name, false); } /** * Checks if the specified string is a valid database name. * * @param name name to be checked * @param glob allow glob syntax * @return result of check */ public static boolean validName(final String name, final boolean glob) { if (name == null) return false; final int nl = name.length(); for (int n = 0; n < nl; n++) { final char ch = name.charAt(n); if ((!glob || ch != '?' && ch != '*' && ch != ',') && !validChar(ch)) return false; } return nl != 0; } }
/** * Extracts the name of a database from the name of a backup. * * @param backup Name of the backup file. Valid formats: {@code [dbname]-yyyy-mm-dd-hh-mm-ss}, * {@code [dbname]} * @return name of the database ({@code [dbname]}) */ public static String name(final String backup) { return Pattern.compile(DateTime.PATTERN + '$').split(backup)[0]; }
/** * Binds the annotated variables. * * @param http http context * @param arg argument array * @throws QueryException query exception * @throws IOException I/O exception */ void bind(final HTTPContext http, final Expr[] arg) throws QueryException, IOException { // bind variables from segments for (int s = 0; s < path.size; s++) { final Matcher m = TEMPLATE.matcher(path.segment[s]); if (!m.find()) continue; final QNm qnm = new QNm(token(m.group(1)), context); bind(qnm, arg, new Atm(http.segment(s))); } // cache request body final String ct = http.contentType(); IOContent body = null; if (requestBody != null) { body = cache(http, null); try { // bind request body in the correct format body.name(http.method + IO.XMLSUFFIX); bind(requestBody, arg, Parser.item(body, context.context.prop, ct)); } catch (final IOException ex) { error(INPUT_CONV, ex); } } // bind query parameters final Map<String, String[]> params = http.params(); for (final RestXqParam rxp : queryParams) bind(rxp, arg, params.get(rxp.key)); // bind form parameters if (!formParams.isEmpty()) { if (MimeTypes.APP_FORM.equals(ct)) { // convert parameters encoded in a form body = cache(http, body); addParams(body.toString(), params); } for (final RestXqParam rxp : formParams) bind(rxp, arg, params.get(rxp.key)); } // bind header parameters for (final RestXqParam rxp : headerParams) { final StringList sl = new StringList(); final Enumeration<?> en = http.req.getHeaders(rxp.key); while (en.hasMoreElements()) { for (final String s : en.nextElement().toString().split(", *")) sl.add(s); } bind(rxp, arg, sl.toArray()); } // bind cookie parameters final Cookie[] ck = http.req.getCookies(); for (final RestXqParam rxp : cookieParams) { String v = null; if (ck != null) { for (final Cookie c : ck) { if (rxp.key.equals(c.getName())) v = c.getValue(); } } if (v == null) bind(rxp, arg); else bind(rxp, arg, v); } }