package org.basex.query; import static org.basex.core.Text.*; import static org.basex.query.util.Err.*; import static org.basex.util.Token.*; import java.io.IOException; import java.io.OutputStream; import java.util.Map.Entry; import org.basex.core.Context; import org.basex.core.Progress; import org.basex.data.Nodes; import org.basex.data.Result; import org.basex.io.serial.Serializer; import org.basex.io.serial.SerializerException; import org.basex.query.expr.Expr; import org.basex.query.func.JavaMapping; import org.basex.query.item.Value; import org.basex.query.iter.Iter; import org.basex.query.util.json.JsonMapConverter; /** * This class is an entry point for evaluating XQuery implementations. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class QueryProcessor extends Progress { /** Expression context. */ public final QueryContext ctx; /** Query. */ private String query; /** Parsed flag. */ private boolean parsed; /** Compilation flag. */ private boolean compiled; /** Closed flag. */ private boolean closed; /** * Default constructor. * @param qu query to process * @param cx database context */ public QueryProcessor(final String qu, final Context cx) { this(qu, cx.current(), cx); } /** * Constructor with an initial context set. * @param qu query * @param nodes initial context set * @param cx database context */ public QueryProcessor(final String qu, final Nodes nodes, final Context cx) { query = qu; ctx = new QueryContext(cx); ctx.nodes = nodes; progress(ctx); } /** * Constructor with an initial context set. * @param qu query * @param o initial context expression * @param cx database context * @throws QueryException query exception */ public QueryProcessor(final String qu, final Object o, final Context cx) throws QueryException { this(qu, o instanceof Expr ? (Expr) o : JavaMapping.toValue(o), cx); } /** * Constructor with an initial context set. * @param qu query * @param expr initial context expression * @param cx database context */ private QueryProcessor(final String qu, final Expr expr, final Context cx) { query = qu; ctx = new QueryContext(cx); ctx.ctxItem = expr; progress(ctx); } /** * Parses the query. * @throws QueryException query exception */ public void parse() throws QueryException { if(parsed) return; ctx.parse(query); parsed = true; } /** * Compiles the query. * @throws QueryException query exception */ public void compile() throws QueryException { parse(); if(compiled) return; ctx.compile(); compiled = true; } /** * Returns a result iterator. * @return result iterator * @throws QueryException query exception */ public Iter iter() throws QueryException { compile(); return ctx.iter(); } /** * Returns a result value. * @return result value * @throws QueryException query exception */ public Value value() throws QueryException { compile(); return ctx.value(); } /** * Evaluates the specified query and returns the result. * @return result of query * @throws QueryException query exception */ public Result execute() throws QueryException { compile(); return ctx.execute(); } /** * Binds a value to a global variable. If the value is an {@link Expr} * instance, it is directly assigned. Otherwise, it is first cast to the * appropriate XQuery type. If {@code "json"} is specified as data type, * the value is interpreted according to the rules specified in * {@link JsonMapConverter}. * @param name name of variable * @param value value to be bound * @param type data type * @return self reference * @throws QueryException query exception */ public QueryProcessor bind(final String name, final Object value, final String type) throws QueryException { ctx.bind(name, value, type); return this; } /** * Binds a value to a global variable. If the value is an {@link Expr} * instance, it is directly assigned. Otherwise, it is first cast to the * appropriate XQuery type. * @param name name of variable * @param value value to be bound * @return self reference * @throws QueryException query exception */ public QueryProcessor bind(final String name, final Object value) throws QueryException { ctx.bind(name, value); return this; } /** * Sets a value as context item. If the value is an {@link Expr} * instance, it is directly assigned. Otherwise, it is first cast to the * appropriate XQuery type. * @param value value to be bound * @return self reference * @throws QueryException query exception */ public QueryProcessor context(final Object value) throws QueryException { ctx.ctxItem = value instanceof Expr ? (Expr) value : JavaMapping.toValue(value); return this; } /** * Declares a namespace. * A namespace is undeclared if the {@code uri} is an empty string. * The default element namespaces is set if the {@code prefix} is empty. * @param prefix namespace prefix * @param uri namespace uri * @return self reference * @throws QueryException query exception */ public QueryProcessor namespace(final String prefix, final String uri) throws QueryException { ctx.sc.namespace(prefix, uri); return this; } /** * Returns a serializer for the given output stream. * Optional output declarations within the query will be included in the * serializer instance. * @param os output stream * @return serializer instance * @throws IOException query exception * @throws QueryException query exception */ public Serializer getSerializer(final OutputStream os) throws IOException, QueryException { compile(); try { return Serializer.get(os, ctx.serProp(true)); } catch(final SerializerException ex) { throw ex.getCause(); } } /** * Evaluates the specified query and returns the result nodes. * @return result nodes * @throws QueryException query exception */ public Nodes queryNodes() throws QueryException { final Result res = execute(); if(!(res instanceof Nodes)) { // convert empty result to node set if(res.size() == 0) return new Nodes(ctx.nodes.data); // otherwise, throw error QUERYNODES.thrw(null); } return (Nodes) res; } /** * Adds a module reference. * @param uri module uri * @param file file name */ public void module(final String uri, final String file) { ctx.modDeclared.add(token(uri), token(file)); } /** * Sets a new query. Should be called before parsing the query. * @param qu query */ public void query(final String qu) { query = qu; parsed = false; compiled = false; } /** * Returns the query string. * @return query */ public String query() { return query; } /** * Closes the processor. * @throws QueryException query exception */ public void close() throws QueryException { // close only once if(closed) return; closed = true; // reset database properties to initial value for(final Entry<String, Object> e : ctx.globalOpt.entrySet()) { ctx.context.prop.set(e.getKey(), e.getValue()); } // close database connections ctx.resource.close(); // close JDBC connections if(ctx.jdbc != null) ctx.jdbc.close(); // close dynamically loaded JAR files if(ctx.jars != null) ctx.jars.close(); } /** * Returns the number of performed updates. * @return number of updates */ public int updates() { return ctx.updates.size(); } /** * Returns query background information. * @return background information */ public String info() { return ctx.info(); } /** * Removes comments from the specified string. * @param qu query string * @param max maximum string length * @return result */ public static String removeComments(final String qu, final int max) { final StringBuilder sb = new StringBuilder(); int m = 0; boolean s = false; final int cl = qu.length(); for(int c = 0; c < cl && sb.length() < max; ++c) { final char ch = qu.charAt(c); if(ch == 0x0d) continue; if(ch == '(' && c + 1 < cl && qu.charAt(c + 1) == ':') { if(m == 0 && !s) { sb.append(' '); s = true; } ++m; ++c; } else if(m != 0 && ch == ':' && c + 1 < cl && qu.charAt(c + 1) == ')') { --m; ++c; } else if(m == 0) { if(ch > ' ') sb.append(ch); else if(!s) sb.append(' '); s = ch <= ' '; } } if(sb.length() >= max) sb.append("..."); return sb.toString().trim(); } /** * Returns the query plan in the dot notation. * @param ser serializer * @throws IOException I/O exception */ public void plan(final Serializer ser) throws IOException { ctx.plan(ser); } @Override public String tit() { return EVALUATING_C; } @Override public String det() { return EVALUATING_C; } }