/** * 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); }
/** * 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); } }
/** * 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; }
/** * 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)); } }
/** * 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); } }
/** * 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); } } } }
/** * 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); } }