package org.basex.query; import static org.basex.query.util.Err.*; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; import org.basex.core.User; import org.basex.core.Commands.CmdPerm; import org.basex.core.cmd.Check; import org.basex.core.cmd.Close; import org.basex.core.cmd.Open; import org.basex.data.Data; import org.basex.data.Nodes; import org.basex.io.IO; import org.basex.query.item.DBNode; import org.basex.query.item.DBNodeSeq; import org.basex.query.item.Empty; import org.basex.query.item.Seq; import org.basex.query.item.Value; import org.basex.util.Array; import org.basex.util.InputInfo; import org.basex.util.Util; import org.basex.util.list.IntList; /** * This class provides access to resources used by an XQuery expression. * * @author BaseX Team 2005-12, BSD License */ public final class QueryResources { /** Database context. */ private final QueryContext ctx; /** Opened databases. */ private Data[] data = new Data[1]; /** Number of databases. */ private int datas; /** Collections: single nodes and sequences. */ private Value[] coll = new Value[1]; /** Names of collections. */ private String[] collName = new String[1]; /** Number of collections. */ private int colls; /** * Constructor. * @param qc query context */ QueryResources(final QueryContext qc) { ctx = qc; } /** * Compiles the resources. * @param nodes input node set * @throws QueryException query exception */ void compile(final Nodes nodes) throws QueryException { final Data d = nodes.data; if(!ctx.context.perm(User.READ, d.meta)) PERMNO.thrw(null, CmdPerm.READ); // assign initial context value // (if database only contains an empty root node, assign empty sequence) ctx.value = d.empty() ? Empty.SEQ : DBNodeSeq.get(new IntList(nodes.list), d, nodes.root, nodes.root); // create default collection: use initial node set if it contains all // documents of the database. otherwise, create new node set addCollection(nodes.root ? ctx.value : DBNodeSeq.get(d.resources.docs(), d, true, true), d.meta.name); addData(d); } /** * Closes the opened data references. * @throws QueryException query exception */ void close() throws QueryException { try { for(int d = ctx.nodes != null ? 1 : 0; d < datas; ++d) { Close.close(data[d], ctx.context); } } catch(final IOException ex) { throw DBCLOSE.thrw(null); } finally { datas = 0; } } /** * Opens a new database or returns a reference to an already opened database. * @param name name of database * @param ii input info * @return database instance * @throws QueryException query exception */ public Data data(final String name, final InputInfo ii) throws QueryException { // check if a database with the same name has already been opened for(int d = 0; d < datas; ++d) { if(data[d].meta.name.equals(name)) return data[d]; } try { // open and add new data reference final Data d = Open.open(name, ctx.context); addData(d); return d; } catch(final IOException ex) { throw NODB.thrw(ii, name); } } /** * Creates a new data reference for the specified input, or returns a * reference to an already opened file or database. * @param input file path or name of database * @param col collection flag * @param ii input info * @return data reference * @throws QueryException query exception */ public Data data(final String input, final boolean col, final InputInfo ii) throws QueryException { // check if an opened database with the same name exists for(int d = 0; d < datas; ++d) { if(data[d].meta.name.equals(input)) return data[d]; } // check if an opened database with the same file path exists final IO io = IO.get(input); for(int d = 0; d < datas; ++d) { if(IO.get(data[d].meta.original).eq(io)) return data[d]; } IOException ex = null; Data d = null; try { // try to retrieve data reference d = Check.check(ctx.context, input); } catch(final IOException e) { ex = e; // try to retrieve data reference relative to base uri final IO base = ctx.sc.baseIO(); if(base != null) { try { final String path = base.merge(input).path(); if(!path.equals(input)) d = Check.check(ctx.context, path); } catch(final IOException exc) { /* ignore exception */ } } } if(d == null) { // handle first exception Util.debug(ex); if(col) NOCOLL.thrw(ii, ex); if(ex instanceof FileNotFoundException) RESFNF.thrw(ii, input); IOERR.thrw(ii, ex); } // add reference to pool of opened databases addData(d); return d; } /** * Adds a collection instance or returns an existing one. * @param input name of the collection to be returned * @param ii input info * @return collection * @throws QueryException query exception */ public Value collection(final String input, final InputInfo ii) throws QueryException { // no collection specified.. return default collection/current context set int c = 0; if(input == null) { // no default collection was defined if(colls == 0) NODEFCOLL.thrw(ii); } else { // invalid collection reference if(input.contains("<") || input.contains("\\")) COLLINV.thrw(ii, input); // find specified collection while(c < colls && !collName[c].equals(input)) ++c; if(c == colls) { final IO base = ctx.sc.baseIO(); if(base != null) { c = 0; final String in = base.merge(input).path(); while(c < colls && !collName[c].equals(in)) ++c; } } // add new collection if not found if(c == colls) { String root = input.replaceFirst("/+$", ""); String path = ""; final int s = root.indexOf('/'); if(s != -1) { path = root.substring(s + 1); root = root.substring(0, s); } addCollection(data(root, true, ii), path); } } return coll[c]; } // API METHODS ============================================================== /** * Adds a document with the specified path. * @param name name of document, or {@code null} * @param path documents path * @throws QueryException query exception */ public void addDoc(final String name, final String path) throws QueryException { final Data d = data(path, false, null); if(name != null) d.meta.name = name; } /** * Adds a collection with the specified paths. * @param name name of collection * @param paths documents paths * @throws QueryException query exception */ public void addCollection(final String name, final String[] paths) throws QueryException { final int ns = paths.length; final DBNode[] nodes = new DBNode[ns]; for(int n = 0; n < ns; n++) { nodes[n] = new DBNode(data(paths[n], true, null), 0, Data.DOC); } addCollection(Seq.get(nodes, ns), name); } // PRIVATE METHODS ========================================================== /** * Adds documents of the specified data reference as a collection. * @param d data reference * @param path inner collection path */ private void addCollection(final Data d, final String path) { addCollection(DBNodeSeq.get(d.resources.docs(path), d, true, path.isEmpty()), d.meta.name); } /** * Adds a data reference to the global list. * @param d data reference to be added */ private void addData(final Data d) { if(datas == data.length) { final Data[] tmp = new Data[Array.newSize(datas)]; System.arraycopy(data, 0, tmp, 0, datas); data = tmp; } data[datas++] = d; } /** * Adds a collection to the global collection list. * @param nodes collection nodes * @param name collection name */ private void addCollection(final Value nodes, final String name) { if(colls == coll.length) { coll = Arrays.copyOf(coll, colls << 1); collName = Array.copyOf(collName, colls << 1); } coll[colls] = nodes; collName[colls++] = name; } }