예제 #1
0
파일: Path.java 프로젝트: runeengh/basex
  /**
   * Checks if the specified step will never yield results.
   *
   * @param rt root value
   * @param s index of step
   * @return {@code true} if steps will never yield results
   */
  private boolean emptyPath(final Value rt, final int s) {
    final Step step = axisStep(s);
    if (step == null) return false;

    final Axis axis = step.axis;
    if (s == 0) {
      // first location step:
      if (root instanceof CAttr) {
        // @.../child:: / @.../descendant::
        if (axis == CHILD || axis == DESC) return true;
      } else if (root instanceof Root
          || root instanceof CDoc
          || rt != null && rt.type == NodeType.DOC) {
        if (axis == SELF || axis == ANCORSELF) {
          if (step.test != Test.NOD && step.test != Test.DOC) return true;
        } else if (axis == CHILD || axis == DESC) {
          if (step.test == Test.DOC || step.test == Test.ATT) return true;
        } else if (axis == DESCORSELF) {
          if (step.test == Test.ATT) return true;
        } else {
          return true;
        }
      }
    } else {
      // remaining steps:
      final Step last = axisStep(s - 1);
      if (last == null) return false;

      // .../self:: / .../descendant-or-self::
      if (axis == SELF || axis == DESCORSELF) {
        if (step.test == Test.NOD) return false;
        // @.../..., text()/...
        if (last.axis == ATTR && step.test.type != NodeType.ATT
            || last.test == Test.TXT && step.test != Test.TXT) return true;
        if (axis == DESCORSELF) return false;

        // .../self::
        final QNm name = step.test.name, lastName = last.test.name;
        if (lastName == null
            || name == null
            || lastName.local().length == 0
            || name.local().length == 0) return false;
        // ...X/...Y
        return !name.eq(lastName);
      }
      // .../following-sibling:: / .../preceding-sibling::
      if (axis == FOLLSIBL || axis == PRECSIBL) return last.axis == ATTR;
      // .../descendant:: / .../child:: / .../attribute::
      if (axis == DESC || axis == CHILD || axis == ATTR)
        return last.axis == ATTR
            || last.test == Test.TXT
            || last.test == Test.COM
            || last.test == Test.PI
            || axis == ATTR && step.test == Test.NSP;
      // .../parent:: / .../ancestor::
      if (axis == PARENT || axis == ANC) return last.test == Test.DOC;
    }
    return false;
  }
예제 #2
0
파일: Path.java 프로젝트: runeengh/basex
  /**
   * Converts descendant to child steps.
   *
   * @param qc query context
   * @param rt root value
   * @return original or new expression
   */
  private Expr children(final QueryContext qc, final Value rt) {
    // skip if index does not exist or is out-dated, or if several namespaces occur in the input
    final Data data = rt.data();
    if (data == null || !data.meta.uptodate || data.nspaces.globalNS() == null) return this;

    Path path = this;
    final int sl = steps.length;
    for (int s = 0; s < sl; s++) {
      // don't allow predicates in preceding location steps
      final Step prev = s > 0 ? axisStep(s - 1) : null;
      if (prev != null && prev.preds.length != 0) break;

      // ignore axes other than descendant, or numeric predicates
      final Step curr = axisStep(s);
      if (curr == null || curr.axis != DESC || curr.has(Flag.FCS)) continue;

      // check if child steps can be retrieved for current step
      ArrayList<PathNode> nodes = pathNodes(data, s);
      if (nodes == null) continue;

      // cache child steps
      final ArrayList<QNm> qnm = new ArrayList<>();
      while (nodes.get(0).parent != null) {
        QNm nm = new QNm(data.elemNames.key(nodes.get(0).name));
        // skip children with prefixes
        if (nm.hasPrefix()) return this;
        for (final PathNode p : nodes) {
          if (nodes.get(0).name != p.name) nm = null;
        }
        qnm.add(nm);
        nodes = PathSummary.parent(nodes);
      }
      qc.compInfo(OPTCHILD, steps[s]);

      // build new steps
      int ts = qnm.size();
      final Expr[] stps = new Expr[ts + sl - s - 1];
      for (int t = 0; t < ts; t++) {
        final Expr[] preds = t == ts - 1 ? ((Preds) steps[s]).preds : new Expr[0];
        final QNm nm = qnm.get(ts - t - 1);
        final NameTest nt =
            nm == null ? new NameTest(false) : new NameTest(nm, Kind.NAME, false, null);
        stps[t] = Step.get(info, CHILD, nt, preds);
      }
      while (++s < sl) stps[ts++] = steps[s];
      path = get(info, root, stps);
      break;
    }

    // check if all steps yield results; if not, return empty sequence
    final ArrayList<PathNode> nodes = pathNodes(qc);
    if (nodes != null && nodes.isEmpty()) {
      qc.compInfo(OPTPATH, path);
      return Empty.SEQ;
    }

    return path;
  }
예제 #3
0
  /**
   * Finds similar function names and throws an error message.
   *
   * @param name function name
   * @param ii input info
   * @throws QueryException query exception
   */
  public void funError(final QNm name, final InputInfo ii) throws QueryException {
    // find global function
    Functions.get().error(name, ii);

    // find similar local function
    final Levenshtein ls = new Levenshtein();
    final byte[] nm = lc(name.local());
    for (final UserFunc f : funcs) {
      if (ls.similar(nm, lc(f.name.local()), 0)) {
        FUNSIMILAR.thrw(ii, name.string(), f.name.string());
      }
    }
  }
예제 #4
0
  /**
   * Adds a local function.
   *
   * @param fun function instance
   * @param ii input info
   * @return function id
   * @throws QueryException query exception
   */
  public int add(final UserFunc fun, final InputInfo ii) throws QueryException {
    final QNm name = fun.name;
    final byte[] uri = name.uri();
    if (uri.length == 0) FUNNONS.thrw(ii, name.string());

    if (NSGlobal.reserved(uri)) {
      if (fun.declared) NAMERES.thrw(ii, name.string());
      funError(name, ii);
    }

    final byte[] ln = name.local();
    for (int l = 0; l < funcs.length; ++l) {
      final QNm qn = funcs[l].name;
      final byte[] u = qn.uri();
      final byte[] nm = qn.local();

      if (eq(ln, nm) && eq(uri, u) && fun.args.length == funcs[l].args.length) {
        // declare function that has been called before
        if (!funcs[l].declared) {
          funcs[l] = fun;
          return l;
        }
        // duplicate declaration
        FUNCDEFINED.thrw(ii, fun.name.string());
      }
    }
    // add function skeleton
    funcs = Array.add(funcs, fun);
    calls = Array.add(calls, new UserFuncCall[0]);
    return funcs.length - 1;
  }
예제 #5
0
  /**
   * Returns the specified function.
   *
   * @param name function qname
   * @param args optional arguments
   * @param ii input info
   * @return function instance
   * @throws QueryException query exception
   */
  public StandardFunc get(final QNm name, final Expr[] args, final InputInfo ii)
      throws QueryException {

    final int id = id(name.id());
    if (id == 0) return null;

    // create function
    final Function fl = funcs[id];
    if (!eq(fl.uri(), name.uri())) return null;

    final StandardFunc f = fl.get(ii, args);
    // check number of arguments
    if (args.length < fl.min || args.length > fl.max) XPARGS.thrw(ii, fl);
    return f;
  }
예제 #6
0
파일: Geo.java 프로젝트: runeengh/basex
  /**
   * Reads an element as a gml node. Returns a geometry element or {@code null} if the element does
   * not match one of the specified types.
   *
   * @param node xml node containing gml object(s)
   * @param names allowed geometry types
   * @return geometry, or {@code null}
   * @throws QueryException query exception
   */
  private static Geometry geo(final ANode node, final QNm... names) throws QueryException {
    if (node.type != NodeType.ELM) throw EXPTYPE_X_X_X.get(null, NodeType.ELM, node.type, node);

    final QNm qname = node.qname();
    for (final QNm geo : names) {
      if (!qname.eq(geo)) continue;
      // type found... create reader and geometry element
      try {
        final String input = node.serialize().toString();
        final GMLReader gmlReader = new GMLReader();
        final GeometryFactory geoFactory = new GeometryFactory();
        return gmlReader.read(input, geoFactory);
      } catch (final Throwable ex) {
        throw GeoErrors.gmlReaderErr(ex);
      }
    }
    return null;
  }
예제 #7
0
 /**
  * Throws an error if one of the pre-defined functions is similar to the specified function name.
  *
  * @param name function name
  * @param ii input info
  * @throws QueryException query exception
  */
 public void error(final QNm name, final InputInfo ii) throws QueryException {
   // compare specified name with names of predefined functions
   final byte[] ln = name.local();
   final Levenshtein ls = new Levenshtein();
   for (int k = 1; k < size; ++k) {
     final int i = indexOf(keys[k], '}');
     final byte[] u = substring(keys[k], 2, i);
     final byte[] l = substring(keys[k], i + 1);
     if (eq(ln, l)) {
       final byte[] ur = name.uri();
       FUNSIMILAR.thrw(
           ii,
           new TokenBuilder(NSGlobal.prefix(ur)).add(':').add(l),
           new TokenBuilder(NSGlobal.prefix(u)).add(':').add(l));
     } else if (ls.similar(ln, l, 0)) {
       FUNSIMILAR.thrw(ii, name.string(), l);
     }
   }
 }
예제 #8
0
  /**
   * Parses a jar descriptor.
   *
   * @param io XML input
   * @return jar descriptor container
   * @throws QueryException query exception
   */
  public JarDesc parse(final IO io) throws QueryException {
    final JarDesc desc = new JarDesc();
    try {
      final ANode node = new DBNode(io).children().next();
      for (final ANode next : node.children()) {
        if (next.type != NodeType.ELM) continue;

        final QNm name = next.qname();
        // ignore namespace to improve compatibility
        if (eq(JAR, name.local())) desc.jars.add(next.string());
        else if (eq(CLASS, name.local())) desc.classes.add(next.string());
        // [CG] Packaging: add warning if unknown elements are encountered
      }
      if (desc.jars.isEmpty()) throw BXRE_JARDESC_X.get(info, NOJARS);
      else if (desc.classes.isEmpty()) throw BXRE_JARDESC_X.get(info, NOCLASSES);
      return desc;
    } catch (final IOException ex) {
      throw BXRE_JARFAIL_X.get(info, ex);
    }
  }
예제 #9
0
  /**
   * Returns a new Java function instance.
   *
   * @param name function name
   * @param args arguments
   * @param qc query context
   * @param sc static context
   * @param ii input info
   * @return Java function or {@code null}
   * @throws QueryException query exception
   */
  static JavaMapping get(
      final QNm name,
      final Expr[] args,
      final QueryContext qc,
      final StaticContext sc,
      final InputInfo ii)
      throws QueryException {

    final byte[] uri = name.uri();
    // check if URI starts with "java:" prefix (if yes, module must be Java code)
    final boolean java = startsWith(uri, JAVAPREF);

    // rewrite function name: convert dashes to upper-case initials
    final String local = camelCase(string(name.local()));

    // check imported Java modules
    final String path = camelCase(toPath(java ? substring(uri, JAVAPREF.length) : uri));

    final ModuleLoader modules = qc.resources.modules();
    final Object jm = modules.findImport(path);
    if (jm != null) {
      final Method meth = getModMethod(jm, path, local, args.length, qc, ii);
      if (meth != null) return new JavaModuleFunc(sc, ii, jm, meth, args);
    }

    // only allowed with administrator permissions
    if (!qc.context.user.has(Perm.ADMIN)) return null;

    // check addressed class
    try {
      return new JavaFunc(sc, ii, modules.findClass(path), local, args);
    } catch (final ClassNotFoundException ex) {
      // only throw exception if "java:" prefix was explicitly specified
      if (java) throw FUNCJAVA_X.get(ii, path);
    } catch (final Throwable th) {
      throw JAVAINIT_X.get(ii, th);
    }

    // no function found
    return null;
  }
예제 #10
0
 /**
  * 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());
 }
예제 #11
0
파일: FuncLit.java 프로젝트: JohnLeM/basex
  @Override
  public void compile(final QueryContext ctx) throws QueryException {
    if (compiled) return;
    compiled = true;

    if (check) {
      final StaticFunc sf = ctx.funcs.get(name, args.length, info);
      if (sf == null) throw Err.FUNCUNKNOWN.thrw(info, name.string());
      type = sf.funcType().seqType();
    }

    final int fp = scope.enter(ctx);
    try {
      expr = expr.compile(ctx, scope);
      expr.markTailCalls();
    } finally {
      scope.cleanUp(this);
      scope.exit(ctx, fp);
    }
  }
예제 #12
0
파일: FuncLit.java 프로젝트: JohnLeM/basex
 @Override
 public String toString() {
   return new TokenBuilder(name.string()).add('#').addExt(args.length).toString();
 }
예제 #13
0
파일: Path.java 프로젝트: nikhi/basex
 /**
  * Checks if the location path contains steps that will never yield results.
  *
  * @param stps step array
  * @param ctx query context
  */
 void voidStep(final Expr[] stps, final QueryContext ctx) {
   for (int l = 0; l < stps.length; ++l) {
     final Step s = axisStep(l);
     if (s == null) continue;
     final Axis sa = s.axis;
     if (l == 0) {
       if (root instanceof CAttr) {
         // @.../child:: / @.../descendant::
         if (sa == CHILD || sa == DESC) {
           ctx.compInfo(WARNDESC, root);
           return;
         }
       } else if (root instanceof Root
           || root instanceof Value && ((Value) root).type == NodeType.DOC
           || root instanceof CDoc) {
         if (sa != CHILD
             && sa != DESC
             && sa != DESCORSELF
             && (sa != SELF && sa != ANCORSELF || s.test != Test.NOD && s.test != Test.DOC)) {
           ctx.compInfo(WARNDOC, root, sa);
           return;
         }
       }
     } else {
       final Step ls = axisStep(l - 1);
       if (ls == null) continue;
       final Axis lsa = ls.axis;
       boolean warning = true;
       if (sa == SELF || sa == DESCORSELF) {
         // .../self:: / .../descendant-or-self::
         if (s.test == Test.NOD) continue;
         // @.../..., text()/...
         warning =
             lsa == ATTR && s.test.type != NodeType.ATT
                 || ls.test == Test.TXT && s.test != Test.TXT;
         if (!warning) {
           if (sa == DESCORSELF) continue;
           // .../self::
           final QNm n0 = ls.test.name;
           final QNm n1 = s.test.name;
           if (n0 == null || n1 == null || n0.local().length == 0 || n1.local().length == 0)
             continue;
           // ...X/...Y
           warning = !n1.eq(n0);
         }
       } else if (sa == FOLLSIBL || sa == PRECSIBL) {
         // .../following-sibling:: / .../preceding-sibling::
         warning = lsa == ATTR;
       } else if (sa == DESC || sa == CHILD || sa == ATTR) {
         // .../descendant:: / .../child:: / .../attribute::
         warning =
             lsa == ATTR
                 || ls.test == Test.TXT
                 || ls.test == Test.COM
                 || ls.test == Test.PI
                 || sa == ATTR && s.test == Test.NSP;
       } else if (sa == PARENT || sa == ANC) {
         // .../parent:: / .../ancestor::
         warning = ls.test == Test.DOC;
       }
       if (warning) {
         ctx.compInfo(WARNSELF, s);
         return;
       }
     }
   }
 }
예제 #14
0
 /**
  * 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();
 }
예제 #15
0
  /**
   * 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;
  }
예제 #16
0
파일: Path.java 프로젝트: nikhi/basex
  /**
   * Converts descendant to child steps.
   *
   * @param ctx query context
   * @param data data reference
   * @return path
   */
  Expr children(final QueryContext ctx, final Data data) {
    // skip path check if no path index exists, or if it is out-of-date
    if (!data.meta.uptodate || data.nspaces.globalNS() == null) return this;

    Path path = this;
    for (int s = 0; s < steps.length; ++s) {
      // don't allow predicates in preceding location steps
      final Step prev = s > 0 ? axisStep(s - 1) : null;
      if (prev != null && prev.preds.length != 0) break;

      // ignore axes other than descendant, or numeric predicates
      final Step curr = axisStep(s);
      if (curr == null || curr.axis != DESC || curr.has(Flag.FCS)) continue;

      // check if child steps can be retrieved for current step
      ArrayList<PathNode> pn = pathNodes(data, s);
      if (pn == null) continue;

      // cache child steps
      final ArrayList<QNm> qnm = new ArrayList<>();
      while (pn.get(0).par != null) {
        QNm nm = new QNm(data.tagindex.key(pn.get(0).name));
        // skip children with prefixes
        if (nm.hasPrefix()) return this;
        for (final PathNode p : pn) {
          if (pn.get(0).name != p.name) nm = null;
        }
        qnm.add(nm);
        pn = PathSummary.parent(pn);
      }
      ctx.compInfo(OPTCHILD, steps[s]);

      // build new steps
      int ts = qnm.size();
      final Expr[] stps = new Expr[ts + steps.length - s - 1];
      for (int t = 0; t < ts; ++t) {
        final Expr[] preds = t == ts - 1 ? ((Preds) steps[s]).preds : new Expr[0];
        final QNm nm = qnm.get(ts - t - 1);
        final NameTest nt =
            nm == null ? new NameTest(false) : new NameTest(nm, Mode.LN, false, null);
        stps[t] = Step.get(info, CHILD, nt, preds);
      }
      while (++s < steps.length) stps[ts++] = steps[s];
      path = get(info, root, stps);
      break;
    }

    // check if the all children in the path exist; don't test with namespaces
    if (data.nspaces.size() == 0) {
      LOOP:
      for (int s = 0; s < path.steps.length; ++s) {
        // only verify child steps; ignore namespaces
        final Step st = path.axisStep(s);
        if (st == null || st.axis != CHILD) break;
        if (st.test.mode == Mode.ALL || st.test.mode == null) continue;
        if (st.test.mode != Mode.LN) break;

        // check if one of the addressed nodes is on the correct level
        final int name = data.tagindex.id(st.test.name.local());
        for (final PathNode pn : data.paths.desc(name, Data.ELEM)) {
          if (pn.level() == s + 1) continue LOOP;
        }
        ctx.compInfo(OPTPATH, path);
        return Empty.SEQ;
      }
    }
    return path;
  }
예제 #17
0
  /**
   * Returns an instance of a with the specified name and number of arguments, or {@code null}.
   *
   * @param name name of the function
   * @param args optional arguments
   * @param dyn compile-/run-time flag
   * @param ctx query context
   * @param ii input info
   * @return function instance
   * @throws QueryException query exception
   */
  public static TypedFunc get(
      final QNm name,
      final Expr[] args,
      final boolean dyn,
      final QueryContext ctx,
      final InputInfo ii)
      throws QueryException {

    // get namespace and local name
    // parse data type constructors
    if (eq(name.uri(), XSURI)) {
      final byte[] ln = name.local();
      final AtomType type = AtomType.find(name, false);
      if (type == null) {
        final Levenshtein ls = new Levenshtein();
        for (final AtomType t : AtomType.values()) {
          if (t.par != null
              && t != AtomType.NOT
              && t != AtomType.AAT
              && t != AtomType.BIN
              && ls.similar(lc(ln), lc(t.string()), 0))
            FUNSIMILAR.thrw(ii, name.string(), t.string());
        }
      }
      // no constructor function found, or abstract type specified
      if (type == null || type == AtomType.NOT || type == AtomType.AAT) {
        FUNCUNKNOWN.thrw(ii, name.string());
      }

      if (args.length != 1) FUNCTYPE.thrw(ii, name.string());
      final SeqType to = SeqType.get(type, Occ.ZERO_ONE);
      return TypedFunc.constr(new Cast(ii, args[0], to), to);
    }

    // pre-defined functions
    final StandardFunc fun = Functions.get().get(name, args, ii);
    if (fun != null) {
      if (!ctx.sc.xquery3 && fun.xquery3()) FEATURE30.thrw(ii);
      for (final Function f : Function.UPDATING) {
        if (fun.sig == f) {
          ctx.updating(true);
          break;
        }
      }
      return new TypedFunc(fun, fun.sig.type(args.length));
    }

    // user-defined function
    final TypedFunc tf = ctx.funcs.get(name, args, ii);
    if (tf != null) return tf;

    // Java function (only allowed with administrator permissions)
    final JavaMapping jf = JavaMapping.get(name, args, ctx, ii);
    if (jf != null) return TypedFunc.java(jf);

    // add user-defined function that has not been declared yet
    if (!dyn && FuncType.find(name) == null) return ctx.funcs.add(name, args, ii, ctx);

    // no function found
    return null;
  }
예제 #18
0
 /**
  * Returns an index to the specified function, or {@code -1}.
  *
  * @param name name of the function
  * @param args optional arguments
  * @return function instance
  */
 private int indexOf(final QNm name, final Expr[] args) {
   for (int id = 0; id < funcs.length; ++id) {
     if (name.eq(funcs[id].name) && args.length == funcs[id].args.length) return id;
   }
   return -1;
 }
예제 #19
0
파일: Geo.java 프로젝트: runeengh/basex
/**
 * This module contains geo spatial functions for the Geo module.
 *
 * @author BaseX Team 2005-14, BSD License
 * @author Masoumeh Seydi
 */
public final class Geo extends QueryModule {
  /** GML URI. */
  private static final byte[] URI = token("http://www.opengis.net/gml");
  /** Prefix: "gml". */
  private static final String GML = "gml";

  /** QName gml:Point. */
  private static final QNm Q_GML_POINT = QNm.get(GML, "Point", URI);
  /** QName gml:MultiPoint. */
  private static final QNm Q_GML_MULTIPOINT = QNm.get(GML, "MultiPoint", URI);
  /** QName gml:LineString. */
  private static final QNm Q_GML_LINESTRING = QNm.get(GML, "LineString", URI);
  /** QName gml:LinearRing. */
  private static final QNm Q_GML_LINEARRING = QNm.get(GML, "LinearRing", URI);
  /** QName gml:Polygon. */
  private static final QNm Q_GML_POLYGON = QNm.get(GML, "Polygon", URI);
  /** QName gml:MultiPolygon. */
  private static final QNm Q_GML_MULTIPOLYGON = QNm.get(GML, "MultiPolygon", URI);
  /** QName gml:MultiLineString. */
  private static final QNm Q_GML_MULTILINESTRING = QNm.get(GML, "MultiLineString", URI);

  /** Array containing all QNames. */
  private static final QNm[] QNAMES = {
    Q_GML_POINT,
    Q_GML_LINESTRING,
    Q_GML_POLYGON,
    Q_GML_MULTIPOINT,
    Q_GML_MULTILINESTRING,
    Q_GML_MULTIPOLYGON,
    Q_GML_LINEARRING
  };

  /**
   * Returns the dimension of an item.
   *
   * @param node xml element containing gml object(s)
   * @return dimension
   * @throws QueryException query exception
   */
  @Deterministic
  public Int dimension(final ANode node) throws QueryException {
    return Int.get(checkGeo(node).getDimension());
  }

  /**
   * Returns the name of the geometry type in the GML namespace, or the empty sequence.
   *
   * @param node xml element containing gml object(s)
   * @return geometry type
   * @throws QueryException query exception
   */
  @Deterministic
  public QNm geometryType(final ANode node) throws QueryException {
    return new QNm(GML + ':' + checkGeo(node).getGeometryType(), URI);
  }

  /**
   * Returns the name of the geometry type in the GML namespace, or the empty sequence.
   *
   * @param node xml element containing gml object(s)
   * @return integer value of CRS of the geometry
   * @throws QueryException query exception
   */
  @Deterministic
  public Uri srid(final ANode node) throws QueryException {
    return Uri.uri(token(checkGeo(node).getSRID()));
  }

  /**
   * Returns the gml:Envelope of the specified geometry. The envelope is the minimum bounding box of
   * this geometry.
   *
   * @param node xml element containing gml object(s)
   * @return envelop element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode envelope(final ANode node) throws QueryException {
    return gmlWriter(checkGeo(node).getEnvelope());
  }

  /**
   * Returns the WKT format of a geometry.
   *
   * @param node xml element containing gml object(s)
   * @return Well-Known Text geometry representation
   * @throws QueryException query exception
   */
  @Deterministic
  public Str asText(final ANode node) throws QueryException {
    return Str.get(new WKTWriter().write(checkGeo(node)));
  }

  /**
   * Returns the WKB format of a geometry.
   *
   * @param node xml element containing gml object(s)
   * @return Well-Known Binary geometry representation
   * @throws QueryException query exception
   */
  @Deterministic
  public B64 asBinary(final ANode node) throws QueryException {
    return new B64(new WKBWriter().write(checkGeo(node)));
  }

  /**
   * Returns a boolean value which shows if the specified geometry is empty or not.
   *
   * @param node xml element containing gml object(s)
   * @return boolean value
   * @throws QueryException query exception
   */
  @Deterministic
  public Bln isEmpty(final ANode node) throws QueryException {
    return Bln.get(node != null && checkGeo(node) != null);
  }

  /**
   * Returns a boolean value which shows if the specified geometry is simple or not, which has no
   * anomalous geometric points, such as self intersection or self tangency.
   *
   * @param node xml element containing gml object(s)
   * @return boolean value
   * @throws QueryException query exception
   */
  @Deterministic
  public Bln isSimple(final ANode node) throws QueryException {
    return Bln.get(checkGeo(node).isSimple());
  }

  /**
   * Returns the boundary of the geometry, in GML. The return value is a sequence of either
   * gml:Point or gml:LinearRing elements.
   *
   * @param node xml element containing gml object(s)
   * @return boundary element (geometry)
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode boundary(final ANode node) throws QueryException {
    return gmlWriter(checkGeo(node).getBoundary());
  }

  /**
   * Returns a boolean value that shows if two geometries are equal or not.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @return boolean value
   * @throws QueryException query exception
   */
  @Deterministic
  public Bln equals(final ANode node1, final ANode node2) throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return Bln.get(geo1.equals(geo2));
  }

  /**
   * Returns a boolean value that shows if this geometry is disjoint to another geometry.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @return boolean value
   * @throws QueryException query exception
   */
  @Deterministic
  public Bln disjoint(final ANode node1, final ANode node2) throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return Bln.get(geo1.disjoint(geo2));
  }

  /**
   * Returns a boolean value that shows if this geometry intersects another geometry.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @return boolean value
   * @throws QueryException query exception
   */
  @Deterministic
  public Bln intersects(final ANode node1, final ANode node2) throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return Bln.get(geo1.intersects(geo2));
  }

  /**
   * Returns a boolean value that shows if this geometry touches the specified geometry.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @return boolean value
   * @throws QueryException query exception
   */
  @Deterministic
  public Bln touches(final ANode node1, final ANode node2) throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return Bln.get(geo1.touches(geo2));
  }

  /**
   * Returns a boolean value that shows if this geometry crosses the specified geometry.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @return boolean value
   * @throws QueryException query exception
   */
  @Deterministic
  public Bln crosses(final ANode node1, final ANode node2) throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return Bln.get(geo1.crosses(geo2));
  }

  /**
   * Returns a boolean value that shows if this geometry is within the specified geometry.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @return boolean value
   * @throws QueryException query exception
   */
  @Deterministic
  public Bln within(final ANode node1, final ANode node2) throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return Bln.get(geo1.within(geo2));
  }

  /**
   * Returns a boolean value that shows if this geometry contains the specified geometry.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @return boolean value
   * @throws QueryException query exception
   */
  @Deterministic
  public Bln contains(final ANode node1, final ANode node2) throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return Bln.get(geo1.contains(geo2));
  }

  /**
   * Returns a boolean value that shows if this geometry overlaps the specified geometry.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @return boolean value
   * @throws QueryException query exception
   */
  @Deterministic
  public Bln overlaps(final ANode node1, final ANode node2) throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return Bln.get(geo1.overlaps(geo2));
  }

  /**
   * Returns a boolean value that shows if whether relationships between the boundaries, interiors
   * and exteriors of two geometries match the pattern specified in intersection-matrix-pattern.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @param intersectionMatrix intersection matrix for two geometries
   * @return boolean value
   * @throws QueryException query exception
   */
  @Deterministic
  public Bln relate(final ANode node1, final ANode node2, final Str intersectionMatrix)
      throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return Bln.get(geo1.relate(geo2, intersectionMatrix.toJava()));
  }

  /**
   * Returns the shortest distance in the units of the spatial reference system of geometry, between
   * the geometries. The distance is the distance between a point on each of the geometries.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @return distance double value
   * @throws QueryException query exception
   */
  @Deterministic
  public Dbl distance(final ANode node1, final ANode node2) throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return Dbl.get(geo1.distance(geo2));
  }

  /**
   * Returns a polygon that represents all Points whose distance from this geometric object is less
   * than or equal to distance. The returned element must be either gml:Polygon, gml:LineString or
   * gml:Point.
   *
   * @param node xml element containing gml object(s)
   * @param distance specific distance from the $geometry (the buffer width)
   * @return buffer geometry as gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode buffer(final ANode node, final Dbl distance) throws QueryException {
    return gmlWriter(checkGeo(node).buffer(distance.dbl()));
  }

  /**
   * Returns the convex hull geometry of a geometry in GML, or the empty sequence. The returned
   * element must be either gml:Polygon, gml:LineString or gml:Point.
   *
   * @param node xml element containing gml object(s)
   * @return convex hull geometry as a gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode convexHull(final ANode node) throws QueryException {
    return gmlWriter(checkGeo(node).convexHull());
  }

  /**
   * Returns a geometric object representing the Point set intersection of two geometries.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @return intersection geometry as a gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode intersection(final ANode node1, final ANode node2) throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return gmlWriter(geo1.intersection(geo2));
  }

  /**
   * Returns a geometric object that represents the Point set union of two geometries.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @return union geometry as a gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode union(final ANode node1, final ANode node2) throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return gmlWriter(geo1.union(geo2));
  }

  /**
   * Returns a geometric object that represents the Point set difference of two geometries.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @return difference geometry as a gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode difference(final ANode node1, final ANode node2) throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return gmlWriter(geo1.difference(geo2));
  }

  /**
   * Returns a geometric object that represents the Point set symmetric difference of two
   * geometries.
   *
   * @param node1 xml element containing gml object(s)
   * @param node2 xml element containing gml object(s)
   * @return symmetric difference geometry as a gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode symDifference(final ANode node1, final ANode node2) throws QueryException {
    final Geometry geo1 = checkGeo(node1);
    final Geometry geo2 = checkGeo(node2);
    return gmlWriter(geo1.symDifference(geo2));
  }

  /**
   * Returns number of geometries in a geometry collection, or 1 if the input is not a collection.
   *
   * @param node xml element containing gml object(s)
   * @return integer value of number of geometries
   * @throws QueryException query exception
   */
  @Deterministic
  public Int numGeometries(final ANode node) throws QueryException {
    return Int.get(checkGeo(node).getNumGeometries());
  }

  /**
   * Returns the nth geometry of a geometry collection, or the geometry if the input is not a
   * collection.
   *
   * @param node xml element containing gml object(s)
   * @param number integer number as the index of nth geometry
   * @return geometry as a gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode geometryN(final ANode node, final Int number) throws QueryException {
    final Geometry geo = checkGeo(node);
    final long n = number.itr();
    if (n < 1 || n > geo.getNumGeometries()) throw GeoErrors.outOfRangeIdx(number);
    return gmlWriter(geo.getGeometryN((int) n - 1));
  }

  /**
   * Returns the x-coordinate value for point.
   *
   * @param node xml element containing gml object(s)
   * @return x double value
   * @throws QueryException query exception
   */
  @Deterministic
  public Dbl x(final ANode node) throws QueryException {
    final Geometry geo = geo(node, Q_GML_POINT);
    if (geo == null && checkGeo(node) != null)
      throw GeoErrors.geoType(node.qname().local(), "Point");

    return Dbl.get(geo.getCoordinate().x);
  }

  /**
   * Returns the y-coordinate value for point.
   *
   * @param node xml element containing gml object(s)
   * @return y double value
   * @throws QueryException query exception
   */
  @Deterministic
  public Dbl y(final ANode node) throws QueryException {
    final Geometry geo = geo(node, Q_GML_POINT);
    if (geo == null && checkGeo(node) != null)
      throw GeoErrors.geoType(node.qname().local(), "Point");

    return Dbl.get(geo.getCoordinate().y);
  }

  /**
   * Returns the z-coordinate value for point.
   *
   * @param node xml element containing gml object(s)
   * @return z double value
   * @throws QueryException query exception
   */
  @Deterministic
  public Dbl z(final ANode node) throws QueryException {
    final Geometry geo = geo(node, Q_GML_POINT);
    if (geo == null && checkGeo(node) != null)
      throw GeoErrors.geoType(node.qname().local(), "Line");

    return Dbl.get(geo.getCoordinate().z);
  }

  /**
   * Returns the length of this Geometry. Linear geometries return their length. Areal geometries
   * return their parameter. Others return 0.0
   *
   * @param node xml element containing gml object(s)
   * @return length double value
   * @throws QueryException query exception
   */
  @Deterministic
  public Dbl length(final ANode node) throws QueryException {
    return Dbl.get(checkGeo(node).getLength());
  }

  /**
   * Returns the start point of a line.
   *
   * @param node xml element containing gml object(s)
   * @return start point geometry as a gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode startPoint(final ANode node) throws QueryException {
    final Geometry geo = geo(node, Q_GML_LINEARRING, Q_GML_LINESTRING);
    if (geo == null && checkGeo(node) != null)
      throw GeoErrors.geoType(node.qname().local(), "Line");

    return gmlWriter(((LineString) geo).getStartPoint());
  }

  /**
   * Returns the end point of a line.
   *
   * @param node xml element containing gml object(s)
   * @return end point geometry as a gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode endPoint(final ANode node) throws QueryException {
    final Geometry geo = geo(node, Q_GML_LINEARRING, Q_GML_LINESTRING);
    if (geo == null && checkGeo(node) != null)
      throw GeoErrors.geoType(node.qname().local(), "Line");

    return gmlWriter(((LineString) geo).getEndPoint());
  }

  /**
   * Checks if the line is closed loop. That is, if the start Point is same with end Point.
   *
   * @param node xml element containing gml object(s)
   * @return boolean value
   * @throws QueryException query exception
   */
  @Deterministic
  public Bln isClosed(final ANode node) throws QueryException {
    final Geometry geo = geo(node, Q_GML_LINEARRING, Q_GML_LINESTRING, Q_GML_MULTILINESTRING);
    if (geo == null && checkGeo(node) != null)
      throw GeoErrors.geoType(node.qname().local(), "Line");

    return Bln.get(
        geo instanceof LineString
            ? ((LineString) geo).isClosed()
            : ((MultiLineString) geo).isClosed());
  }

  /**
   * Return a boolean value that shows weather the line is a ring or not. A line is a ring if it is
   * closed and simple.
   *
   * @param node xml element containing gml object(s)
   * @return boolean value
   * @throws QueryException query exception
   */
  @Deterministic
  public Bln isRing(final ANode node) throws QueryException {
    final Geometry geo = geo(node, Q_GML_LINEARRING, Q_GML_LINESTRING);
    if (geo == null && checkGeo(node) != null)
      throw GeoErrors.geoType(node.qname().local(), "Line");

    return Bln.get(((LineString) geo).isRing());
  }

  /**
   * Returns the number of points in a geometry.
   *
   * @param node xml element containing gml object(s)
   * @return number of points int value
   * @throws QueryException query exception
   */
  @Deterministic
  public Int numPoints(final ANode node) throws QueryException {
    return Int.get(checkGeo(node).getNumPoints());
  }

  /**
   * Returns the nth point of a line.
   *
   * @param node xml element containing gml object(s)
   * @param number index of i-th point
   * @return n-th point as a gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode pointN(final ANode node, final Int number) throws QueryException {
    final Geometry geo = geo(node, Q_GML_LINEARRING, Q_GML_LINESTRING);
    if (geo == null && checkGeo(node) != null)
      throw GeoErrors.geoType(node.qname().local(), "Line");

    final int max = geo.getNumPoints();
    final long n = number.itr();
    if (n < 1 || n > max) throw GeoErrors.outOfRangeIdx(number);

    return gmlWriter(((LineString) geo).getPointN((int) n - 1));
  }

  /**
   * Returns the area of a Geometry. Areal Geometries have a non-zero area. Returns zero for Point
   * and Lines.
   *
   * @param node xml element containing gml object(s)
   * @return geometry area as a double vaue
   * @throws QueryException query exception
   */
  @Deterministic
  public Dbl area(final ANode node) throws QueryException {
    return Dbl.get(checkGeo(node).getArea());
  }

  /**
   * Returns the mathematical centroid of the geometry as a gml:Point. The point is not guaranteed
   * to be on the surface.
   *
   * @param node xml element containing gml object(s)
   * @return centroid geometry as a gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode centroid(final ANode node) throws QueryException {
    return gmlWriter(checkGeo(node).getCentroid());
  }

  /**
   * Returns a gml:Point that is interior of this geometry. If it cannot be inside the geometry,
   * then it will be on the boundary.
   *
   * @param node xml element containing gml object(s)
   * @return a point as a gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode pointOnSurface(final ANode node) throws QueryException {
    return gmlWriter(checkGeo(node).getInteriorPoint());
  }

  /**
   * Returns the outer ring of a polygon, in GML.
   *
   * @param node xml element containing gml object(s)
   * @return exterior ring geometry (LineString) as a gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode exteriorRing(final ANode node) throws QueryException {
    final Geometry geo = geo(node, Q_GML_POLYGON);
    if (geo == null && checkGeo(node) != null)
      throw GeoErrors.geoType(node.qname().local(), "Polygon");

    return gmlWriter(((Polygon) geo).getExteriorRing());
  }

  /**
   * Returns the number of interior rings in a polygon.
   *
   * @param node xml element containing gml object(s)
   * @return integer number of interior rings
   * @throws QueryException query exception
   */
  @Deterministic
  public Int numInteriorRing(final ANode node) throws QueryException {
    final Geometry geo = geo(node, Q_GML_POLYGON);
    if (geo == null && checkGeo(node) != null)
      throw GeoErrors.geoType(node.qname().local(), "Polygon");

    return Int.get(((Polygon) geo).getNumInteriorRing());
  }

  /**
   * Returns the nth geometry of a geometry collection.
   *
   * @param node xml element containing gml object(s)
   * @param number index of i-th interior ring
   * @return n-th interior ring geometry (LineString) as a gml element
   * @throws QueryException query exception
   */
  @Deterministic
  public ANode interiorRingN(final ANode node, final Int number) throws QueryException {
    final Geometry geo = geo(node, Q_GML_POLYGON);
    if (geo == null && checkGeo(node) != null)
      throw GeoErrors.geoType(node.qname().local(), "Polygon");

    final long n = number.itr();
    final int max = ((Polygon) geo).getNumInteriorRing();
    if (n < 1 || n > max) throw GeoErrors.outOfRangeIdx(number);
    return gmlWriter(((Polygon) geo).getInteriorRingN((int) n - 1));
  }

  // PRIVATE METHODS (hidden from user of module) ========================================

  /**
   * Reads an element as a gml node. Returns a geometry element or throws an exception if the
   * element is of the wrong type.
   *
   * @param node xml node containing gml object(s)
   * @return geometry
   * @throws QueryException query exception
   */
  private Geometry checkGeo(final ANode node) throws QueryException {
    final Geometry geo = geo(node, QNAMES);
    if (geo == null) throw GeoErrors.unrecognizedGeo(node.qname().local());
    return geo;
  }

  /**
   * Reads an element as a gml node. Returns a geometry element or {@code null} if the element does
   * not match one of the specified types.
   *
   * @param node xml node containing gml object(s)
   * @param names allowed geometry types
   * @return geometry, or {@code null}
   * @throws QueryException query exception
   */
  private static Geometry geo(final ANode node, final QNm... names) throws QueryException {
    if (node.type != NodeType.ELM) throw EXPTYPE_X_X_X.get(null, NodeType.ELM, node.type, node);

    final QNm qname = node.qname();
    for (final QNm geo : names) {
      if (!qname.eq(geo)) continue;
      // type found... create reader and geometry element
      try {
        final String input = node.serialize().toString();
        final GMLReader gmlReader = new GMLReader();
        final GeometryFactory geoFactory = new GeometryFactory();
        return gmlReader.read(input, geoFactory);
      } catch (final Throwable ex) {
        throw GeoErrors.gmlReaderErr(ex);
      }
    }
    return null;
  }

  /**
   * Writes an geometry and returns a string representation of the geometry.
   *
   * @param geometry geometry
   * @return DBNode database node
   * @throws QueryException exception
   */
  private DBNode gmlWriter(final Geometry geometry) throws QueryException {
    final String geo;
    try {
      // write geometry and add namespace declaration
      geo =
          new GMLWriter()
              .write(geometry)
              .replaceAll("^<gml:(.*?)>", "<gml:$1 xmlns:gml='" + string(URI) + "'>");
    } catch (final Exception ex) {
      throw GeoErrors.gmlWriterErr(ex);
    }

    try {
      final IO io = new IOContent(geo);
      return new DBNode(MemBuilder.build(new XMLParser(io, queryContext.context.options)));
    } catch (final IOException ex) {
      throw IOERR_X.get(null, ex);
    }
  }
}
예제 #20
0
 /**
  * Checks if the specified item is a string or element.
  *
  * @param it item to be checked
  * @return item
  * @throws QueryException query exception
  */
 private Item checkElmStr(final Item it) throws QueryException {
   if (it instanceof AStr || TEST.eq(it)) return it;
   throw ELMSTRTYPE.thrw(info, Q_ENTRY.string(), it.type);
 }