Exemple #1
0
  /**
   * 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);
  }
Exemple #2
0
  /**
   * 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()]);
  }
Exemple #3
0
  /**
   * 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;
    }
  }
Exemple #4
0
 /**
  * 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);
 }
Exemple #5
0
  /**
   * 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;
  }
Exemple #6
0
  /**
   * 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);
  }
Exemple #7
0
  /**
   * 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);
    }
  }
Exemple #8
0
  /**
   * 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);
  }
Exemple #9
0
  /**
   * 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;
  }
Exemple #10
0
 /**
  * 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;
 }
Exemple #11
0
  /**
   * 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);
    }
  }
Exemple #12
0
/**
 * 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;
    }
  }
}
Exemple #13
0
/**
 * 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));
  }
}
Exemple #14
0
/**
 * 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));
  }
}
Exemple #15
0
 /**
  * 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());
 }
Exemple #16
0
/**
 * 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);
      }
    }
  }
}
Exemple #17
0
/**
 * 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);
  }
}
Exemple #18
0
/**
 * 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()]);
  }
}
Exemple #19
0
/**
 * 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;
  }
}
Exemple #20
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];
 }
Exemple #21
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);
    }
  }