diff -Nru basex-8.1.1/basex-api/pom.xml basex-8.2.3/basex-api/pom.xml --- basex-8.1.1/basex-api/pom.xml 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/pom.xml 2015-07-14 10:54:40.000000000 +0000 @@ -7,7 +7,7 @@ org.basex basex-parent - 8.1.1 + 8.2.3 .. diff -Nru basex-8.1.1/basex-api/.settings/checkstyle.xml basex-8.2.3/basex-api/.settings/checkstyle.xml --- basex-8.1.1/basex-api/.settings/checkstyle.xml 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/.settings/checkstyle.xml 2015-07-14 10:54:40.000000000 +0000 @@ -1,12 +1,12 @@ - - @@ -26,7 +26,7 @@ - + @@ -50,7 +50,6 @@ - diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/api/xmldb/BXCollection.java basex-8.2.3/basex-api/src/main/java/org/basex/api/xmldb/BXCollection.java --- basex-8.1.1/basex-api/src/main/java/org/basex/api/xmldb/BXCollection.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/api/xmldb/BXCollection.java 2015-07-14 10:54:40.000000000 +0000 @@ -10,7 +10,6 @@ import org.basex.core.*; import org.basex.core.cmd.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.io.*; import org.basex.util.*; import org.basex.util.list.*; diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/api/xmldb/BXDatabase.java basex-8.2.3/basex-api/src/main/java/org/basex/api/xmldb/BXDatabase.java --- basex-8.1.1/basex-api/src/main/java/org/basex/api/xmldb/BXDatabase.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/api/xmldb/BXDatabase.java 2015-07-14 10:54:40.000000000 +0000 @@ -76,8 +76,8 @@ final String main = uri.startsWith(XMLDBC) ? uri : XMLDBC + uri; if(main.startsWith(XMLDBURI)) { final String host = main.substring(XMLDBURI.length()); - final String localhost = S_LOCALHOST + ':' + - ctx.soptions.get(StaticOptions.SERVERPORT) + '/'; + final int port = ctx.soptions.get(StaticOptions.SERVERPORT); + final String localhost = S_LOCALHOST + ':' + port + '/'; if(host.startsWith(localhost)) return host.substring(localhost.length()); } } diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/api/xmldb/BXQueryService.java basex-8.2.3/basex-api/src/main/java/org/basex/api/xmldb/BXQueryService.java --- basex-8.1.1/basex-api/src/main/java/org/basex/api/xmldb/BXQueryService.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/api/xmldb/BXQueryService.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,7 +4,6 @@ import java.util.*; -import org.basex.data.*; import org.basex.query.*; import org.basex.query.value.*; import org.basex.query.value.node.*; @@ -71,7 +70,8 @@ @Override public BXResourceSet query(final String query) throws XMLDBException { final DBNodes nodes = coll.ctx.current(); - return query(query, DBNodeSeq.get(new IntList(nodes.pres), nodes.data, nodes.all, nodes.all)); + final boolean all = nodes.all(); + return query(query, DBNodeSeq.get(new IntList(nodes.pres()), nodes.data(), all, all)); } @Override diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/BaseXHTTP.java basex-8.2.3/basex-api/src/main/java/org/basex/BaseXHTTP.java --- basex-8.1.1/basex-api/src/main/java/org/basex/BaseXHTTP.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/BaseXHTTP.java 2015-07-14 10:54:40.000000000 +0000 @@ -97,8 +97,8 @@ } // start web server in a new process + final Connector connector = jetty.getConnectors()[0]; if(service) { - final Connector connector = jetty.getConnectors()[0]; start(connector.getPort(), connector instanceof SslSelectChannelConnector, args); for(final Connector c : jetty.getConnectors()) { @@ -110,10 +110,10 @@ } // request password on command line if only the user was specified - if(!Options.getSystem(StaticOptions.USER).isEmpty()) { - while(Options.getSystem(StaticOptions.PASSWORD).isEmpty()) { + if(!Prop.get(StaticOptions.USER).isEmpty()) { + while(Prop.get(StaticOptions.PASSWORD).isEmpty()) { Util.out(PASSWORD + COLS); - Options.setSystem(StaticOptions.PASSWORD, Util.password()); + Prop.put(StaticOptions.PASSWORD, Util.password()); } } @@ -121,7 +121,7 @@ try { jetty.start(); } catch(final BindException ex) { - throw new IOException(HTTP + ' ' + SRV_RUNNING, ex); + throw new IOException(Util.info(HTTP + ' ' + SRV_RUNNING_X, connector.getPort()), ex); } // throw cached exception that did not break the servlet architecture final IOException ex = HTTPContext.exception(); @@ -174,9 +174,7 @@ // server has been started in a separate process and needs to be stopped if(!bool(StaticOptions.HTTPLOCAL, mprop)) { - final int port = num(StaticOptions.SERVERPORT, mprop); - final int eport = num(StaticOptions.EVENTPORT, mprop); - BaseXServer.stop(port, eport); + BaseXServer.stop(num(StaticOptions.SERVERPORT, mprop)); } } @@ -187,7 +185,7 @@ * @return numeric value */ private static int num(final NumberOption option, final StaticOptions sopts) { - final String val = Options.getSystem(option); + final String val = Prop.get(option); return val.isEmpty() ? sopts.get(option) : Strings.toInt(val); } @@ -198,7 +196,7 @@ * @return boolean value */ private static boolean bool(final BooleanOption option, final StaticOptions sopts) { - final String val = Options.getSystem(option); + final String val = Prop.get(option); return val.isEmpty() ? sopts.get(option) : Boolean.parseBoolean(val); } @@ -268,45 +266,42 @@ if(arg.dash()) { switch(arg.next()) { case 'd': // activate debug mode - Options.setSystem(StaticOptions.DEBUG, true); + Prop.put(StaticOptions.DEBUG, true); Prop.debug = true; break; case 'D': // hidden flag: daemon mode serve = false; break; - case 'e': // parse event port - Options.setSystem(StaticOptions.EVENTPORT, arg.number()); - break; case 'h': // parse HTTP port httpPort = arg.number(); break; case 'l': // use local mode - Options.setSystem(StaticOptions.HTTPLOCAL, true); + Prop.put(StaticOptions.HTTPLOCAL, true); break; case 'n': // parse host name final String n = arg.string(); - Options.setSystem(StaticOptions.HOST, n); - Options.setSystem(StaticOptions.SERVERHOST, n); + Prop.put(StaticOptions.HOST, n); + Prop.put(StaticOptions.SERVERHOST, n); break; case 'p': // parse server port final int p = arg.number(); - Options.setSystem(StaticOptions.PORT, p); - Options.setSystem(StaticOptions.SERVERPORT, p); + Prop.put(StaticOptions.PORT, p); + Prop.put(StaticOptions.SERVERPORT, p); break; case 'P': // specify password - Options.setSystem(StaticOptions.PASSWORD, arg.string()); + Prop.put(StaticOptions.PASSWORD, arg.string()); break; case 's': // parse stop port - Options.setSystem(StaticOptions.STOPPORT, arg.number()); + Prop.put(StaticOptions.STOPPORT, arg.number()); break; case 'S': // set service flag service = serve; break; case 'U': // specify user name - Options.setSystem(StaticOptions.USER, arg.string()); + Prop.put(StaticOptions.USER, arg.string()); break; case 'z': // suppress logging - Options.setSystem(StaticOptions.LOG, false); + Prop.put(StaticOptions.LOG, false); break; default: throw arg.usage(); @@ -336,7 +331,7 @@ if(ping(S_LOCALHOST, port, ssl)) return; Performance.sleep(c * 100L); } - throw new BaseXException(CONNECTION_ERROR); + throw new BaseXException(CONNECTION_ERROR_X, port); } /** @@ -360,9 +355,10 @@ new Socket(S_LOCALHOST, port).close(); // give the notified process some time to quit Performance.sleep(100); - } catch(final IOException ex) { + } catch(final ConnectException ex) { + throw new IOException(Util.info(CONNECTION_ERROR_X, port)); + } finally { stop.delete(); - throw ex; } } diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/http/HTTPCode.java basex-8.2.3/basex-api/src/main/java/org/basex/http/HTTPCode.java --- basex-8.1.1/basex-api/src/main/java/org/basex/http/HTTPCode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/http/HTTPCode.java 2015-07-14 10:54:40.000000000 +0000 @@ -27,6 +27,8 @@ NO_PATH(SC_NOT_FOUND, "No path specified."), /** Error: 404, "No function found to process the request.". */ NO_XQUERY(SC_NOT_FOUND, "No function found that matches the request."), + /** Error: 404, "RESTXQ directory not found.". */ + NO_RESTXQ(SC_NOT_FOUND, "RESTXQ directory not found."), /** Error 501, "Method not supported: %.". */ NOT_IMPLEMENTED_X(SC_NOT_IMPLEMENTED, "Method not supported: %."); diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/http/HTTPContext.java basex-8.2.3/basex-api/src/main/java/org/basex/http/HTTPContext.java --- basex-8.1.1/basex-api/src/main/java/org/basex/http/HTTPContext.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/http/HTTPContext.java 2015-07-14 10:54:40.000000000 +0000 @@ -24,7 +24,6 @@ import org.basex.util.*; import org.basex.util.http.*; import org.basex.util.http.HttpText.Request; -import org.basex.util.options.*; /** * Bundles context-based information on a single HTTP operation. @@ -175,7 +174,7 @@ } /** - * Returns the URL path. + * Returns the URL path. The path always starts with a slash. * @return path path */ public String path() { @@ -376,6 +375,43 @@ context.log.write(address(), context.user(), type, info, perf); } + /** + * Sends a redirect. + * @param location location + * @throws IOException I/O exception + */ + public void redirect(final String location) throws IOException { + res.sendRedirect(resolve(location)); + } + + /** + * Normalizes a redirection location. Prefixes absolute locations with the request URI. + * @param location location + * @return normalized representation + */ + public String resolve(final String location) { + String loc = location; + if(location.startsWith("/")) { + final String uri = req.getRequestURI(), info = req.getPathInfo(); + if(info == null) { + loc = uri + location; + } else { + loc = uri.substring(0, uri.length() - info.length()) + location; + } + } + return loc; + } + + /** + * Sends a forward. + * @param location location + * @throws IOException I/O exception + * @throws ServletException servlet exception + */ + public void forward(final String location) throws IOException, ServletException { + req.getRequestDispatcher(resolve(location)).forward(req, res); + } + // STATIC METHODS ===================================================================== /** @@ -400,8 +436,8 @@ // set web application path as home directory and HTTPPATH final String webapp = sc.getRealPath("/"); - Options.setSystem(Prop.PATH, webapp); - Options.setSystem(StaticOptions.WEBPATH, webapp); + System.setProperty(Prop.PATH, webapp); + Prop.put(StaticOptions.WEBPATH, webapp); // bind all parameters that start with "org.basex." to system properties final Enumeration en = sc.getInitParameterNames(); @@ -415,7 +451,7 @@ Util.debug(key.toUpperCase(Locale.ENGLISH) + ": " + val); val = new IOFile(webapp, val).path(); } - Options.setSystem(key, val); + Prop.put(key, val); } // create context, update options @@ -441,13 +477,15 @@ * Closes the database context. */ public static synchronized void close() { - if(server == null) return; - try { - server.stop(); - } catch(final IOException ex) { - Util.stack(ex); + if(server != null) { + try { + server.stop(); + } catch(final IOException ex) { + Util.stack(ex); + } + server = null; } - server = null; + context.close(); } /** diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/http/HTTPParams.java basex-8.2.3/basex-api/src/main/java/org/basex/http/HTTPParams.java --- basex-8.1.1/basex-api/src/main/java/org/basex/http/HTTPParams.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/http/HTTPParams.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,7 +9,6 @@ import org.basex.io.*; import org.basex.io.in.*; import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.util.*; @@ -89,7 +88,7 @@ for(final Entry entry : map().entrySet()) { final String key = entry.getKey(); final String[] values = entry.getValue(); - final ValueBuilder vb = new ValueBuilder(values.length); + final ValueBuilder vb = new ValueBuilder(); for(final String v : values) vb.add(new Atm(v)); query.put(key, vb.value()); } diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqFunction.java basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqFunction.java --- basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqFunction.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqFunction.java 2015-07-14 10:54:40.000000000 +0000 @@ -18,14 +18,15 @@ import org.basex.build.json.*; import org.basex.build.text.*; import org.basex.core.*; +import org.basex.core.Context; import org.basex.http.*; import org.basex.io.serial.*; import org.basex.query.*; +import org.basex.query.ann.*; import org.basex.query.expr.*; import org.basex.query.expr.path.*; import org.basex.query.expr.path.Test.Kind; import org.basex.query.func.*; -import org.basex.query.iter.*; import org.basex.query.util.*; import org.basex.query.value.*; import org.basex.query.value.item.*; @@ -114,65 +115,68 @@ /** * Checks a function for RESTFful annotations. - * @param http HTTP context + * @param ctx database context * @return {@code true} if module contains relevant annotations * @throws Exception exception */ - boolean parse(final HTTPContext http) throws Exception { + boolean parse(final Context ctx) throws Exception { // parse all annotations final boolean[] declared = new boolean[function.args.length]; boolean found = false; - final MainOptions options = http.context(false).options; + final MainOptions options = ctx.options; for(final Ann ann : function.anns) { - found |= eq(ann.sig.uri, QueryText.REST_URI); - if(ann.sig == _REST_PATH) { + final Annotation sig = ann.sig; + if(sig == null) continue; + + found |= eq(sig.uri, QueryText.REST_URI); + if(sig == _REST_PATH) { try { path = new RestXqPath(toString(ann.args[0]), ann.info); } catch(final IllegalArgumentException ex) { throw error(ann.info, ex.getMessage()); } for(final QNm v : path.vars()) checkVariable(v, AtomType.AAT, declared); - } else if(ann.sig == _REST_ERROR) { + } else if(sig == _REST_ERROR) { error(ann); - } else if(ann.sig == _REST_CONSUMES) { + } else if(sig == _REST_CONSUMES) { strings(ann, consumes); - } else if(ann.sig == _REST_PRODUCES) { + } else if(sig == _REST_PRODUCES) { strings(ann, produces); - } else if(ann.sig == _REST_QUERY_PARAM) { + } else if(sig == _REST_QUERY_PARAM) { queryParams.add(param(ann, declared)); - } else if(ann.sig == _REST_FORM_PARAM) { + } else if(sig == _REST_FORM_PARAM) { formParams.add(param(ann, declared)); - } else if(ann.sig == _REST_HEADER_PARAM) { + } else if(sig == _REST_HEADER_PARAM) { headerParams.add(param(ann, declared)); - } else if(ann.sig == _REST_COOKIE_PARAM) { + } else if(sig == _REST_COOKIE_PARAM) { cookieParams.add(param(ann, declared)); - } else if(ann.sig == _REST_ERROR_PARAM) { + } else if(sig == _REST_ERROR_PARAM) { errorParams.add(param(ann, declared)); - } else if(ann.sig == _REST_METHOD) { + } else if(sig == _REST_METHOD) { final String mth = toString(ann.args[0]).toUpperCase(Locale.ENGLISH); final Item body = ann.args.length > 1 ? ann.args[1] : null; addMethod(mth, body, declared, ann.info); - } else if(eq(ann.sig.uri, QueryText.REST_URI)) { + } else if(eq(sig.uri, QueryText.REST_URI)) { final Item body = ann.args.length == 0 ? null : ann.args[0]; - addMethod(string(ann.sig.local()), body, declared, ann.info); - } else if(ann.sig == _INPUT_CSV) { + addMethod(string(sig.local()), body, declared, ann.info); + } else if(sig == _INPUT_CSV) { final CsvParserOptions opts = new CsvParserOptions(options.get(MainOptions.CSVPARSER)); options.set(MainOptions.CSVPARSER, parse(opts, ann)); - } else if(ann.sig == _INPUT_JSON) { + } else if(sig == _INPUT_JSON) { final JsonParserOptions opts = new JsonParserOptions(options.get(MainOptions.JSONPARSER)); options.set(MainOptions.JSONPARSER, parse(opts, ann)); - } else if(ann.sig == _INPUT_HTML) { + } else if(sig == _INPUT_HTML) { final HtmlOptions opts = new HtmlOptions(options.get(MainOptions.HTMLPARSER)); options.set(MainOptions.HTMLPARSER, parse(opts, ann)); - } else if(ann.sig == _INPUT_TEXT) { + } else if(sig == _INPUT_TEXT) { final TextOptions opts = new TextOptions(options.get(MainOptions.TEXTPARSER)); options.set(MainOptions.TEXTPARSER, parse(opts, ann)); - } else if(eq(ann.sig.uri, QueryText.OUTPUT_URI)) { + } else if(eq(sig.uri, QueryText.OUTPUT_URI)) { // serialization parameters try { - output.assign(string(ann.sig.local()), toString(ann.args[0])); + output.assign(string(sig.local()), toString(ann.args[0])); } catch(final BaseXException ex) { - throw error(ann.info, UNKNOWN_SER, ann.sig.local()); + throw error(ann.info, UNKNOWN_SER, sig.local()); } } } @@ -498,7 +502,7 @@ final QNm qnm = checkVariable(toString(args[1]), declared); // default value final int al = args.length; - final ValueBuilder vb = new ValueBuilder(al); + final ValueBuilder vb = new ValueBuilder(); for(int a = 2; a < al; a++) vb.add(args[a]); return new RestXqParam(qnm, key, vb.value()); } diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqModule.java basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqModule.java --- basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqModule.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqModule.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,6 +6,7 @@ import java.io.*; import java.util.*; +import org.basex.core.*; import org.basex.http.*; import org.basex.io.*; import org.basex.query.*; @@ -44,14 +45,15 @@ functions.clear(); // loop through all functions - try(final QueryContext qc = qc(http)) { + final Context ctx = http.context(false); + try(final QueryContext qc = qc(ctx)) { // loop through all functions final String name = file.name(); for(final StaticFunc uf : qc.funcs.funcs()) { // only add functions that are defined in the same module (file) if(name.equals(new IOFile(uf.info.path()).name())) { final RestXqFunction rxf = new RestXqFunction(uf, qc, this); - if(rxf.parse(http)) functions.add(rxf); + if(rxf.parse(ctx)) functions.add(rxf); } } } @@ -92,9 +94,10 @@ throws Exception { // create new XQuery instance - try(final QueryContext qc = qc(http)) { + final Context ctx = http.context(false); + try(final QueryContext qc = qc(ctx)) { final RestXqFunction rxf = new RestXqFunction(find(qc, func.function), qc, this); - rxf.parse(http); + rxf.parse(ctx); RestXqResponse.create(rxf, qc, http, error); } } @@ -103,12 +106,12 @@ /** * Retrieves a query context for the given module. - * @param http HTTP context + * @param ctx database context * @return query context * @throws Exception exception */ - private QueryContext qc(final HTTPContext http) throws Exception { - final QueryContext qc = new QueryContext(http.context(false)); + private QueryContext qc(final Context ctx) throws Exception { + final QueryContext qc = new QueryContext(ctx); try { qc.parse(string(file.read()), file.path(), null); return qc; diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqModules.java basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqModules.java --- basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqModules.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqModules.java 2015-07-14 10:54:40.000000000 +0000 @@ -24,8 +24,6 @@ /** Module cache. */ private HashMap modules = new HashMap<>(); - /** RESTXQ path. */ - private IOFile restxq; /** Private constructor. */ private RestXqModules() { } @@ -38,6 +36,13 @@ } /** + * Initializes the module cache. + */ + public void init() { + modules = new HashMap<>(); + } + + /** * Returns a WADL description for all available URIs. * @param http HTTP context * @return WADL description @@ -55,11 +60,9 @@ * @throws Exception exception (including unexpected ones) */ RestXqFunction find(final HTTPContext http, final QNm error) throws Exception { - cache(http); - // collect all functions final ArrayList list = new ArrayList<>(); - for(final RestXqModule mod : modules.values()) { + for(final RestXqModule mod : cache(http).values()) { for(final RestXqFunction rxf : mod.functions()) { if(rxf.matches(http, error)) list.add(rxf); } @@ -128,20 +131,27 @@ /** * Updates the module cache. Parses new modules and discards obsolete ones. * @param http http context + * @return module cache * @throws Exception exception (including unexpected ones) */ - private synchronized void cache(final HTTPContext http) throws Exception { - // initialize RESTXQ directory (may be relative against WEBPATH) - if(restxq == null) { - final StaticOptions sopts = http.context(false).soptions; + private synchronized HashMap cache(final HTTPContext http) + throws Exception { + + final StaticOptions sopts = http.context(false).soptions; + HashMap cache = modules; + + // create new cache if it is empty, or if cache is to be recreated every time + if(cache.isEmpty() || !sopts.get(StaticOptions.CACHERESTXQ)) { + cache = new HashMap<>(); final String webpath = sopts.get(StaticOptions.WEBPATH); final String rxqpath = sopts.get(StaticOptions.RESTXQPATH); - restxq = new IOFile(webpath).resolve(rxqpath); + final IOFile restxq = new IOFile(webpath).resolve(rxqpath); + if(!restxq.exists()) throw HTTPCode.NO_RESTXQ.get(); + + cache(http, restxq, cache, modules); + modules = cache; } - // create new cache - final HashMap cache = new HashMap<>(); - cache(http, restxq, cache); - modules = cache; + return cache; } /** @@ -149,18 +159,24 @@ * @param root root path * @param http http context * @param cache cached modules + * @param old old cache * @throws Exception exception (including unexpected ones) */ - private synchronized void cache(final HTTPContext http, final IOFile root, - final HashMap cache) throws Exception { + private static synchronized void cache(final HTTPContext http, final IOFile root, + final HashMap cache, final HashMap old) + throws Exception { + + // check if directory is to be skipped + final IOFile[] files = root.children(); + for(final IOFile file : files) if(file.name().equals(IGNORE)) return; - for(final IOFile file : root.children()) { + for(final IOFile file : files) { if(file.isDir()) { - cache(http, file, cache); + cache(http, file, cache, old); } else { final String path = file.path(); if(file.hasSuffix(IO.XQSUFFIXES)) { - RestXqModule module = modules.get(path); + RestXqModule module = old.get(path); boolean parsed = false; if(module != null) { // check if module has been modified diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqRespBuilder.java basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqRespBuilder.java --- basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqRespBuilder.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqRespBuilder.java 2015-07-14 10:54:40.000000000 +0000 @@ -67,12 +67,12 @@ final byte[] nam = c.attribute(Q_NAME); final byte[] val = c.attribute(Q_VALUE); if(nam != null && val != null) { - final String key = string(nam); - final String value = string(val); - if(key.equals(HttpText.CONTENT_TYPE)) { + final String key = string(nam), value = string(val); + if(key.equalsIgnoreCase(HttpText.CONTENT_TYPE)) { cType = value; } else { - http.res.setHeader(key, value); + http.res.setHeader(key, key.equalsIgnoreCase(HttpText.LOCATION) ? + http.resolve(value) : value); } } } else { diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqResponse.java basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqResponse.java --- basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqResponse.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqResponse.java 2015-07-14 10:54:40.000000000 +0000 @@ -96,9 +96,9 @@ query.context.unregister(query); if(redirect != null) { - http.res.sendRedirect(redirect); + http.redirect(redirect); } else if(forward != null) { - http.req.getRequestDispatcher(forward).forward(http.req, http.res); + http.forward(forward); } else if(response != null) { if(response.status != 0) http.status(response.status, response.message, response.error); final byte[] out = response.cache.finish(); diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqServlet.java basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqServlet.java --- basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqServlet.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqServlet.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,5 +1,7 @@ package org.basex.http.restxq; +import static org.basex.http.restxq.RestXqText.*; + import org.basex.http.*; import org.basex.query.*; @@ -18,17 +20,30 @@ public final class RestXqServlet extends BaseXServlet { @Override protected void run(final HTTPContext http) throws Exception { + // no trailing slash: send redirect + if(http.req.getPathInfo() == null) { + http.redirect("/"); + return; + } + // analyze input path final RestXqModules rxm = RestXqModules.get(); - // select XQuery function + + // initialize RESTXQ + if(http.path().equals("/" + INIT)) { + rxm.init(); + return; + } + + // select function that closest matches the request RestXqFunction func = rxm.find(http, null); if(func == null) throw HTTPCode.NO_XQUERY.get(); try { - // process function that matches the current request + // process function func.process(http, null); } catch(final QueryException ex) { - // process optional error function + // run optional error function func = rxm.find(http, ex.qname()); if(func == null) throw ex; func.process(http, ex); diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqText.java basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqText.java --- basex-8.1.1/basex-api/src/main/java/org/basex/http/restxq/RestXqText.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/http/restxq/RestXqText.java 2015-07-14 10:54:40.000000000 +0000 @@ -44,6 +44,10 @@ String WADL_URI = "http://wadl.dev.java.net/2009/02"; /** XHTML namespace. */ String XHTML_URL = "http://www.w3.org/1999/xhtml"; + /** Ignore directory. */ + String IGNORE = ".ignore"; + /** Init call. */ + String INIT = ".init"; /** Error message. */ String ANN_MISSING = "Annotation %% or %% missing."; diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/http/ServletListener.java basex-8.2.3/basex-api/src/main/java/org/basex/http/ServletListener.java --- basex-8.1.1/basex-api/src/main/java/org/basex/http/ServletListener.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/http/ServletListener.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,15 +1,13 @@ package org.basex.http; import javax.servlet.*; -import javax.servlet.annotation.*; /** - *

Servlet listener.

+ * This class creates and destroys servlet contexts. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ -@WebListener public final class ServletListener implements ServletContextListener { @Override public void contextInitialized(final ServletContextEvent event) { diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/http/SessionListener.java basex-8.2.3/basex-api/src/main/java/org/basex/http/SessionListener.java --- basex-8.1.1/basex-api/src/main/java/org/basex/http/SessionListener.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/http/SessionListener.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,7 +5,7 @@ import javax.servlet.http.*; /** - * This class bundles context-based information on a single HTTP operation. + * This class creates and destroys HTTP sessions. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/http/webdav/impl/WebDAVService.java basex-8.2.3/basex-api/src/main/java/org/basex/http/webdav/impl/WebDAVService.java --- basex-8.1.1/basex-api/src/main/java/org/basex/http/webdav/impl/WebDAVService.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/http/webdav/impl/WebDAVService.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,7 +4,6 @@ import static org.basex.query.func.Function.*; import java.io.*; -import java.text.*; import java.util.*; import java.util.Map.Entry; import java.util.List; @@ -114,14 +113,7 @@ public long timestamp(final String db) throws IOException { final WebDAVQuery query = new WebDAVQuery(DATA.args(_DB_INFO.args("$path") + "/descendant::" + DbFn.toName(Text.TIMESTAMP) + "[1]")).bind("path", db); - - try { - // retrieve and parse timestamp - return DateTime.parse(execute(query).get(0), DateTime.DATETIME).getTime(); - } catch(final ParseException ex) { - Util.errln(ex); - return 0L; - } + return DateTime.parse(execute(query).get(0)).getTime(); } /** @@ -146,7 +138,7 @@ final StringList result = execute(query); final boolean raw = Boolean.parseBoolean(result.get(0)); final MediaType type = new MediaType(result.get(1)); - final long mod = DateTime.parse(result.get(2)); + final long mod = DateTime.parse(result.get(2)).getTime(); final Long size = raw ? Long.valueOf(result.get(3)) : null; final String pth = stripLeadingSlash(result.get(4)); return new ResourceMetaData(db, pth, mod, raw, type, size); @@ -320,7 +312,7 @@ for(int r = 0; r < rs; r += 5) { final boolean raw = Boolean.parseBoolean(result.get(r)); final MediaType ctype = new MediaType(result.get(r + 1)); - final long mod = DateTime.parse(result.get(r + 2)); + final long mod = DateTime.parse(result.get(r + 2)).getTime(); final Long size = raw ? Long.valueOf(result.get(r + 3)) : null; final String pth = stripLeadingSlash(result.get(r + 4)); final int ix = pth.indexOf(SEP); @@ -351,7 +343,7 @@ final List dbs = new ArrayList<>(rs >>> 1); for(int r = 0; r < rs; r += 2) { final String name = result.get(r); - final long mod = DateTime.parse(result.get(r + 1)); + final long mod = DateTime.parse(result.get(r + 1)).getTime(); dbs.add(factory.database(this, new ResourceMetaData(name, mod))); } return dbs; diff -Nru basex-8.1.1/basex-api/src/main/java/org/basex/modules/Session.java basex-8.2.3/basex-api/src/main/java/org/basex/modules/Session.java --- basex-8.1.1/basex-api/src/main/java/org/basex/modules/Session.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/basex/modules/Session.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,7 +7,6 @@ import org.basex.data.*; import org.basex.http.*; import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -99,7 +98,7 @@ */ @Requires(Permission.NONE) public void set(final Str key, final Value value) throws QueryException { - final ValueBuilder vb = new ValueBuilder(Math.max(1, (int) value.size())); + final ValueBuilder vb = new ValueBuilder(); for(final Item item : value) { if(item instanceof FItem) throw SessionErrors.functionItem(); final Data d = item.data(); diff -Nru basex-8.1.1/basex-api/src/main/java/org/exquery/ns/Request.java basex-8.2.3/basex-api/src/main/java/org/exquery/ns/Request.java --- basex-8.1.1/basex-api/src/main/java/org/exquery/ns/Request.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/java/org/exquery/ns/Request.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,7 +7,6 @@ import org.basex.http.*; import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.seq.*; diff -Nru basex-8.1.1/basex-api/src/main/python3/BaseXClient.py basex-8.2.3/basex-api/src/main/python3/BaseXClient.py --- basex-8.1.1/basex-api/src/main/python3/BaseXClient.py 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/python3/BaseXClient.py 2015-07-14 10:54:40.000000000 +0000 @@ -112,11 +112,6 @@ self.__swrapper.connect((host, port)) - self.__event_socket_wrapper = None - self.__event_host = host - self.__event_listening_thread = None - self.__event_callbacks = {} - # receive timestamp response = self.recv_c_str().split(':') @@ -185,76 +180,6 @@ """Close the session""" self.send('exit') self.__swrapper.close() - if not self.__event_socket_wrapper is None: - self.__event_socket_wrapper.close() - - def __register_and_start_listener(self, - receive_bytes_encoding=None, - send_bytes_encoding=None): - """register and start listener.""" - - if receive_bytes_encoding: - receive_bytes_encoding_ = receive_bytes_encoding - else: - receive_bytes_encoding_ = self.__swrapper.receive_bytes_encoding - if send_bytes_encoding: - send_bytes_encoding_ = send_bytes_encoding - else: - send_bytes_encoding_ = self.__swrapper.send_bytes_encoding - - self.__swrapper.sendall(chr(10)) - event_port = int(self.recv_c_str()) - self.__event_socket_wrapper = SocketWrapper( - socket.socket(socket.AF_INET, socket.SOCK_STREAM), - receive_bytes_encoding=receive_bytes_encoding_, - send_bytes_encoding=send_bytes_encoding_) - self.__event_socket_wrapper.settimeout(5000) - self.__event_socket_wrapper.connect((self.__event_host, event_port)) - token = self.recv_c_str() - self.__event_socket_wrapper.sendall(token + chr(0)) - #if self.__event_socket_wrapper.recv_single_byte() != chr(0): - # raise IOError("Could not register event listener") - #java example client does skip next byte... - ign = self.__event_socket_wrapper.recv_single_byte() - - self.__event_listening_thread = threading.Thread( - target=self.__event_listening_loop - ) - self.__event_listening_thread.daemon = True - self.__event_listening_thread.start() - - def __event_listening_loop(self): - """event listening loop (in subthread)""" - reader = self.__event_socket_wrapper - reader.clear_buffer() - while True: - name = reader.recv_until_terminator() - data = reader.recv_until_terminator() - self.__event_callbacks[name](data) - - def is_listening(self): - """true if registered and started listener, false otherwise""" - return not self.__event_socket_wrapper is None - - def watch(self, name, callback): - """Watch the specified event""" - if not self.is_listening(): - self.__register_and_start_listener() - else: - self.__swrapper.sendall(chr(10)) - self.send(name) - info = self.recv_c_str() - if not self.server_response_success(): - raise IOError(info) - self.__event_callbacks[name] = callback - - def unwatch(self, name): - """Unwatch the specified event""" - self.send(chr(11) + name) - info = self.recv_c_str() - if not self.server_response_success(): - raise IOError(info) - del self.__event_callbacks[name] def recv_c_str(self): """Retrieve a string from the socket""" diff -Nru basex-8.1.1/basex-api/src/main/python3/WatchEventExample.py basex-8.2.3/basex-api/src/main/python3/WatchEventExample.py --- basex-8.1.1/basex-api/src/main/python3/WatchEventExample.py 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/python3/WatchEventExample.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# Documentation: http://docs.basex.org/wiki/Events -# -# (C) BaseX Team 2005-12, BSD License - -import BaseXClient -import time -from datetime import datetime -from multiprocessing import Process - -# event publisher process (child) -def child(): - # create session (listener) - session = BaseXClient.Session('localhost', 1984, 'admin', 'admin') - - # trigger event (first) - session.execute("""xquery db:event("MY_EVENT", "fired MY_EVENT from child, at %s")""" % str(datetime.now())) - - # Zzz... - time.sleep(3) - - # trigger event (second) - session.execute("""xquery db:event("MY_EVENT", "fired MY_EVENT from child, at %s")""" % str(datetime.now())) - - # close session - session.close() - -# listener function -def dump_my_event(data): - print(data) - -# listener process main -def main(): - # create session (listener) - session = BaseXClient.Session('localhost', 1984, 'admin', 'admin') - - # create event - session.execute("create event MY_EVENT"); - - try: - # register event watcher - session.watch("MY_EVENT", dump_my_event) - - # fork child - chp = Process(target=child, args=()) - chp.start() - chp.join() - - # unregister event watcher - session.unwatch("MY_EVENT") - - finally: - # drop event - session.execute("drop event MY_EVENT"); - - # close session - session.close() - -# test it! -main() diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/common.xqm basex-8.2.3/basex-api/src/main/webapp/dba/common.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/common.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/common.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -13,10 +13,10 @@ : Redirects to the start page. :) declare - %rest:path("dba") + %rest:path("/dba") function _:redirect( ) { - web:redirect('dba/databases') + web:redirect("/dba/databases") }; (:~ @@ -25,11 +25,11 @@ : @return rest response and binary file :) declare - %rest:path("dba/files/{$file=.+}") + %rest:path("/dba/static/{$file=.+}") function _:file( $file as xs:string ) as item()+ { - let $path := file:base-dir() || 'files/' || $file + let $path := file:base-dir() || 'static/' || $file return ( web:response-header(map { 'media-type': web:content-type($path) }), file:read-binary($path) @@ -42,7 +42,7 @@ : @return page :) declare - %rest:path("dba/{$unknown}") + %rest:path("/dba/{$unknown}") %output:method("html") function _:any( $unknown as xs:string diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/alter-db.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/alter-db.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/alter-db.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/alter-db.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -24,7 +24,7 @@ :) declare %rest:GET - %rest:path("dba/alter-db") + %rest:path("/dba/alter-db") %rest:query-param("name", "{$name}") %rest:query-param("newname", "{$newname}") %rest:query-param("error", "{$error}") @@ -70,7 +70,7 @@ declare %updating %rest:POST - %rest:path("dba/alter-db") + %rest:path("/dba/alter-db") %rest:query-param("name", "{$name}") %rest:query-param("newname", "{$newname}") function _:alter( diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/backups.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/backups.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/backups.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/backups.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -18,7 +18,7 @@ declare %updating %rest:GET - %rest:path("dba/create-backup") + %rest:path("/dba/create-backup") %rest:query-param("name", "{$name}") function _:create-backup( $name as xs:string @@ -34,7 +34,7 @@ declare %updating %rest:GET - %rest:path("dba/drop-backup") + %rest:path("/dba/drop-backup") %rest:query-param("name", "{$name}") %rest:query-param("backup", "{$backups}") function _:drop-backup( @@ -54,7 +54,7 @@ declare %updating %rest:GET - %rest:path("dba/restore") + %rest:path("/dba/restore") %rest:query-param("name", "{$name}") %rest:query-param("backup", "{$backup}") function _:restore( diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/copy.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/copy.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/copy.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/copy.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -24,7 +24,7 @@ :) declare %rest:GET - %rest:path("dba/copy") + %rest:path("/dba/copy") %rest:query-param("name", "{$name}") %rest:query-param("newname", "{$newname}") %rest:query-param("error", "{$error}") @@ -70,7 +70,7 @@ declare %updating %rest:POST - %rest:path("dba/copy") + %rest:path("/dba/copy") %rest:query-param("name", "{$name}") %rest:query-param("newname", "{$newname}") function _:copy( diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/create-db.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/create-db.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/create-db.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/create-db.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -25,7 +25,7 @@ :) declare %rest:GET - %rest:path("dba/create-db") + %rest:path("/dba/create-db") %rest:query-param("name", "{$name}") %rest:query-param("opts", "{$opts}", "textindex", "attrindex") %rest:query-param("lang", "{$lang}", "en") @@ -93,7 +93,7 @@ declare %updating %rest:POST - %rest:path("dba/create-db") + %rest:path("/dba/create-db") %rest:query-param("name", "{$name}") %rest:query-param("opts", "{$opts}") %rest:query-param("lang", "{$lang}") diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/databases.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/databases.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/databases.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/databases.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -22,7 +22,7 @@ :) declare %rest:GET - %rest:path("dba/databases") + %rest:path("/dba/databases") %rest:query-param("sort", "{$sort}", "") %rest:query-param("error", "{$error}") %rest:query-param("info", "{$info}") @@ -110,7 +110,7 @@ :) declare %rest:POST - %rest:path("dba/databases") + %rest:path("/dba/databases") %rest:query-param("action", "{$action}") %rest:query-param("name", "{$names}") %output:method("html") diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/database.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/database.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/database.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/database.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -25,7 +25,7 @@ :) declare %rest:GET - %rest:path("dba/database") + %rest:path("/dba/database") %rest:query-param("name", "{$name}") %rest:query-param("resource", "{$resource}") %rest:query-param("error", "{$error}") @@ -51,7 +51,7 @@ ) else (), element backups { db:backups($name) } ) - }', map { 'name': $name, 'max': $cons:MAX-ROWS + 1 }) + }', map { 'name': $name, 'max': $cons:OPTION($cons:K-MAX-ROWS) + 1 }) } catch * { element error { $cons:DATA-ERROR || ': ' || $err:description } } @@ -71,11 +71,12 @@ let $entries := $data/databases/* ! + }' size='{ @size }'/> let $headers := ( { html:label($entries, ('Resource', 'Resources')) }, Content type, - Raw + Raw, + Size (factor) ) let $buttons := ( html:button('add', 'Add…'), @@ -149,7 +150,7 @@ :) declare %rest:POST - %rest:path("dba/database") + %rest:path("/dba/database") %rest:form-param("action", "{$action}") %rest:form-param("name", "{$name}") %rest:form-param("resource", "{$resources}") diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/drop-db.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/drop-db.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/drop-db.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/drop-db.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -18,7 +18,7 @@ declare %updating %rest:GET - %rest:path("dba/drop-db") + %rest:path("/dba/drop-db") %rest:query-param("name", "{$names}") %output:method("html") function _:drop( diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/optimize.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/optimize.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/optimize.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/optimize.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -26,7 +26,7 @@ :) declare %rest:GET - %rest:path("dba/optimize") + %rest:path("/dba/optimize") %rest:query-param("name", "{$name}") %rest:query-param("all", "{$all}") %rest:query-param("opts", "{$opts}") @@ -108,7 +108,7 @@ declare %updating %rest:POST - %rest:path("dba/optimize") + %rest:path("/dba/optimize") %rest:query-param("name", "{$name}") %rest:query-param("all", "{$all}") %rest:query-param("opts", "{$opts}") @@ -149,7 +149,7 @@ declare %updating %rest:GET - %rest:path("dba/optimize-all") + %rest:path("/dba/optimize-all") %rest:query-param("name", "{$names}") %output:method("html") function _:drop( @@ -157,7 +157,7 @@ ) { cons:check(), try { - util:update("$names ! db:optimize(.)", map { 'n': $names }), + util:update("$n ! db:optimize(.)", map { 'n': $names }), db:output(web:redirect($_:CAT, map { 'info': 'Optimized databases: ' || count($names) })) } catch * { db:output(web:redirect($_:CAT, map { 'error': $err:description })) diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/add.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/add.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/add.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/add.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -25,7 +25,7 @@ :) declare %rest:GET - %rest:path("dba/add") + %rest:path("/dba/add") %rest:query-param("name", "{$name}") %rest:query-param("resource", "{$resource}") %rest:query-param("binary", "{$binary}") @@ -83,7 +83,7 @@ declare %updating %rest:POST - %rest:path("dba/add") + %rest:path("/dba/add") %rest:form-param("name", "{$name}") %rest:form-param("resource", "{$resource}") %rest:form-param("file", "{$file}") diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/delete.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/delete.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/delete.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/delete.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -19,7 +19,7 @@ declare %updating %rest:GET - %rest:path("dba/delete") + %rest:path("/dba/delete") %rest:query-param("name", "{$name}") %rest:query-param("resource", "{$resources}") %output:method("html") diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/download.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/download.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/download.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/download.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -16,7 +16,7 @@ : @return rest response and file content :) declare - %rest:path("dba/download/{$file}") + %rest:path("/dba/download/{$file}") %rest:query-param("name", "{$name}") %rest:query-param("resource", "{$resource}") function _:download( @@ -50,7 +50,7 @@ : @return zip file :) declare - %rest:path("dba/backup/{$backup}") + %rest:path("/dba/backup/{$backup}") %output:method("raw") %output:media-type("application/octet-stream") function _:download( diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/query.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/query.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/query.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/query.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -16,11 +16,10 @@ : @return result string :) declare - %rest:POST - %rest:path("dba/query-resource") + %rest:POST("{$query}") + %rest:path("/dba/query-resource") %rest:query-param("name", "{$name}") %rest:query-param("resource", "{$resource}") - %rest:query-param("query", "{$query}") %output:method("text") function _:query-resource( $name as xs:string, @@ -28,7 +27,6 @@ $query as xs:string ) as xs:string { cons:check(), - let $limit := $cons:MAX-CHARS let $query := if($query) then $query else '.' return util:query($query, "'': db:open($name, $resource)", map { 'name': $name, 'resource': $resource diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/rename.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/rename.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/rename.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/rename.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -25,7 +25,7 @@ :) declare %rest:GET - %rest:path("dba/rename") + %rest:path("/dba/rename") %rest:query-param("name", "{$name}") %rest:query-param("resource", "{$resource}") %rest:query-param("newname", "{$newname}") @@ -76,7 +76,7 @@ declare %updating %rest:POST - %rest:path("dba/rename") + %rest:path("/dba/rename") %rest:query-param("name", "{$name}") %rest:query-param("resource", "{$resource}") %rest:query-param("newname", "{$newname}") diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/replace.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/replace.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/replace.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/replace.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -24,7 +24,7 @@ :) declare %rest:GET - %rest:path("dba/replace") + %rest:path("/dba/replace") %rest:query-param("name", "{$name}") %rest:query-param("resource", "{$resource}") %rest:query-param("error", "{$error}") @@ -71,7 +71,7 @@ declare %updating %rest:POST - %rest:path("dba/replace") + %rest:path("/dba/replace") %rest:form-param("name", "{$name}") %rest:form-param("resource", "{$resource}") %rest:form-param("input", "{$input}") diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/resource.xqm basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/resource.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/databases/resources/resource.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/databases/resources/resource.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -20,7 +20,7 @@ :) declare %rest:POST - %rest:path("dba/resource") + %rest:path("/dba/resource") %rest:form-param("action", "{$action}") %rest:form-param("name", "{$name}") %rest:form-param("resource", "{$resource}") diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/files/basex.svg basex-8.2.3/basex-api/src/main/webapp/dba/files/basex.svg --- basex-8.1.1/basex-api/src/main/webapp/dba/files/basex.svg 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/files/basex.svg 1970-01-01 00:00:00.000000000 +0000 @@ -1,78 +0,0 @@ - -image/svg+xml - \ No newline at end of file diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/files/config.xml basex-8.2.3/basex-api/src/main/webapp/dba/files/config.xml --- basex-8.1.1/basex-api/src/main/webapp/dba/files/config.xml 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/files/config.xml 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ - - 10000 - 500 - 5 - 100 - admin - \ No newline at end of file diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/files/delete-files.xqm basex-8.2.3/basex-api/src/main/webapp/dba/files/delete-files.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/files/delete-files.xqm 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/files/delete-files.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,32 @@ +(:~ + : Delete files. + : + : @author Christian Grün, BaseX GmbH, 2014-15 + :) +module namespace _ = 'dba/files'; + +import module namespace cons = 'dba/cons' at '../modules/cons.xqm'; + +(:~ Top category :) +declare variable $_:CAT := 'files'; + +(:~ + : Deletes files. + : @param $names names of files + :) +declare + %rest:GET + %rest:path("/dba/delete-files") + %rest:query-param("name", "{$names}") + %output:method("html") +function _:drop( + $names as xs:string* +) { + cons:check(), + try { + $names ! file:delete($cons:DBA-DIR || .), + web:redirect($_:CAT, map { 'info': 'Deleted files: ' || count($names) }) + } catch * { + web:redirect($_:CAT, map { 'error': $err:description }) + } +}; diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/files/files.xqm basex-8.2.3/basex-api/src/main/webapp/dba/files/files.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/files/files.xqm 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/files/files.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,107 @@ +(:~ + : Files page. + : + : @author Christian Grün, BaseX GmbH, 2014-15 + :) +module namespace _ = 'dba/files'; + +import module namespace Sessions = 'http://basex.org/modules/sessions'; +import module namespace Session = 'http://basex.org/modules/session'; +import module namespace cons = 'dba/cons' at '../modules/cons.xqm'; +import module namespace html = 'dba/html' at '../modules/html.xqm'; +import module namespace tmpl = 'dba/tmpl' at '../modules/tmpl.xqm'; + +(:~ Top category :) +declare variable $_:CAT := 'files'; + +(:~ + : Files page. + : @param $sort table sort key + : @param $error error message + : @param $info info message + : @return page + :) +declare + %rest:GET + %rest:path("/dba/files") + %rest:query-param("sort", "{$sort}", "") + %rest:query-param("error", "{$error}") + %rest:query-param("info", "{$info}") + %output:method("html") +function _:files( + $sort as xs:string, + $error as xs:string?, + $info as xs:string? +) as element() { + cons:check(), + + (: request data in a single step :) + let $files := file:children($cons:DBA-DIR)[file:is-file(.)] + + return tmpl:wrap(map { 'top': $_:CAT, 'info': $info, 'error': $error }, + + +
+

Files

+ { + let $entries := + for $file in $files + let $date := file:last-modified($file) + return + let $headers := ( + { html:label($entries, ('File', 'Files')) }, + Date, + Size + ) + let $buttons := html:button('delete-files', 'Delete', true()) + let $link := function($value) { 'file/' || $value } + return html:table($entries, $headers, $buttons, map {}, $sort, $link) + } + +

Upload Files

+
+ + +
+
+ The files are located on the DBA system in the temporary directory + { $cons:DBA-DIR }. +
+
 
+ + + ) +}; + +(:~ + : Redirects to the specified action. + : @param $action action to perform + : @param $names names of files + :) +declare + %rest:POST + %rest:path("/dba/files") + %rest:query-param("action", "{$action}") + %rest:query-param("name", "{$names}") + %output:method("html") +function _:action( + $action as xs:string, + $names as xs:string* +) { + web:redirect($action, map { 'name': $names, 'redirect': $_:CAT }) +}; + +(:~ + : Downloads a file. + : @param $name name of file + : @return file + :) +declare + %rest:GET + %rest:path("/dba/file/{$name}") +function _:files( + $name as xs:string +) as item()+ { + web:response-header(), + file:read-binary($cons:DBA-DIR || $name) +}; diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/files/js.js basex-8.2.3/basex-api/src/main/webapp/dba/files/js.js --- basex-8.1.1/basex-api/src/main/webapp/dba/files/js.js 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/files/js.js 1970-01-01 00:00:00.000000000 +0000 @@ -1,130 +0,0 @@ -function buttons() { - var forms = document.getElementsByTagName("form"); - for(var f = 0; f < forms.length; f++) { - var form = forms[f]; - if(form.className != 'update') continue; - - var inputs = form.getElementsByTagName("input"); - var c = 0; - for(var i = 0; i < inputs.length; i++) { - if(inputs[i].type == "checkbox" && inputs[i].checked) c++; - } - var buttons = form.getElementsByTagName("button"); - for(var i = 0; i < buttons.length; i++) { - var button = buttons[i], n = button.className, s = button.value, e = !button.disabled; - if(n == 'global') continue; - - if(s == "optimize" || s == "optimize-all" || s == "drop-backup" || - s == "drop-db" || s == "drop-pattern" || s == "drop-user" || - s == "kill-session" || s == "restore" || s == "backup" || s == "delete" || - s == "delete-logs" || s == "kill") { - e = c > 0; - } - button.disabled = !e; - } - } -}; - -function setInfo(message) { - setText(message, 'info'); -}; - -function setWarning(message) { - setText(message, 'warning'); -}; - -function setError(message) { - setText(message.replace(/Stack Trace:.*/, ''), 'error'); -}; - -function setText(message, type) { - var i = document.getElementById("info"); - i.className = type; - i.textContent = message; -}; - -var searchDelay = 200; -var _d; -function query(wait, success, key, query, enforce, target) { - var d = new Date(); - _d = d; - setTimeout(function() { - if(_d != d) return; - - var name = document.getElementById("name"); - var resource = document.getElementById("resource"); - var sort = document.getElementById("sort"); - var loglist = document.getElementById("loglist"); - - if(wait) setWarning(wait); - var req = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); - req.onreadystatechange = function() { - if(_d != d) return; - if(req.readyState == 4) { - if(req.status == 200) { - target(req.responseText); - if(success) setInfo(success); - } else { - setError(req.statusText.replace(/^.*?\] /, '')); - } - } - }; - // synchronous querying: wait for server feedback - req.open("POST", key + - "?name=" + encodeURIComponent(name ? name.value : "") + - "&resource=" + encodeURIComponent(resource ? resource.value : "") + - "&sort=" + encodeURIComponent(sort ? sort.value : "") + - "&loglist=" + encodeURIComponent(loglist ? loglist.value : "") + - "&query=" + encodeURIComponent(query), true); - req.send(null); - }, enforce ? 0 : searchDelay); -}; - -var _list; -function logslist(wait, success) { - var input = document.getElementById('loglist').value.trim(); - if(_list == input) return false; - _list = input; - query(wait, success, 'loglist', input, false, function(text) { - document.getElementById("list").innerHTML = text; - }) -}; - -var _logs; -function logentries(wait, success) { - var input = document.getElementById('logs').value.trim(); - if(_logs == input) return false; - _logs = input; - query(wait, success, 'log', input, false, function(text) { - document.getElementById("output").innerHTML = text; - }); -}; - -var _input; -function queryResource(wait, success) { - var input = document.getElementById('input').value.trim(); - if(_input == input) return false; - _input = input; - var target = 'query-resource'; - var enforce = false; - query(wait, success, target, input, enforce, function(text) { - document.getElementById("output").value = text; - }); -}; - -var _editor; -function xquery(wait, success, enforce) { - var mode = document.getElementById("mode").selectedIndex; - var update = mode == 2; - var realtime = mode == 1; - document.getElementById("run").disabled = realtime; - - var editor = document.getElementById('editor').value; - if(enforce || (realtime && _editor != editor)) { - _editor = editor; - var target = update ? 'update-query' : 'eval-query'; - query(wait, success, target, editor, enforce, function(text) { - document.getElementById("output").value = text; - }); - } -}; diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/files/style.css basex-8.2.3/basex-api/src/main/webapp/dba/files/style.css --- basex-8.1.1/basex-api/src/main/webapp/dba/files/style.css 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/files/style.css 1970-01-01 00:00:00.000000000 +0000 @@ -1,142 +0,0 @@ -body { - font-family: Calibri, "Trebuchet MS", sans-serif; - overflow-y: scroll; - margin: 1em; - font-size: 16px; -} - -ul, dd { - padding-left: 1.2em; - margin-left: 0; -} - -dl { - margin: 0; -} - -dt { - margin: 0.6em 0 0.6em 0; - font-weight: bold; -} - -table { - font-size: 100%; - padding: 0; - border: 0; - table-layout: fixed; -} - -th { - padding: 0 1.2em .2em 0; -} - -td { - padding: 0 1.2em 0 0; - vertical-align: top; - border: 0; -} - -textarea { - font-family: "Consolas", "Ubuntu Mono", monospace; - font-size: 90%; - margin: 4px 0px 4px 0px; - width: 100%; -} - -select { - width: 166; -} - -code { - font-family: "Consolas", "Ubuntu Mono", monospace; - border: 1px solid #E3E3FF; - border-radius: 3px; - padding: 0 4px 0 4px; - margin: 0 2px 0 2px; - font-size: 90%; -} - -pre { - font-family: "Consolas", "Ubuntu Mono", monospace; - font-size: 90%; -} - -h1 { - font-size: 220%; - font-weight: bold; - color: #AA1818; - margin: -2px 0 0 0; -} - -h2 { - font-size: 150%; - color: #CC3333; - margin: 0 0 0.2em 0; -} - -h3 { - font-size: 120%; - color: #CC3333; - margin: 0.2em 0 0.2em 0; -} - -h4 { - font-size: 100%; - margin: 0 0 0.4em 0; -} - -hr { - height: 1px; - border: 0px; - background-color: #777; -} - -a { - color: #CC3333; - text-decoration: none; -} - -a:hover { - text-decoration:underline; -} - -p { - margin: 0 0 0.6em 0; -} - -.right { - float: right; -} - -.error { - color: #FF3333; - font-weight: bold; -} - -.info { - color: #009900; - font-weight: bold; -} - -.warning { - color: #999999; - font-weight: bold; -} - -.note { - color: #666666; -} - -.small { - height: 10px; -} - -.variable { - font-family: "Calibri", "Trebuchet MS", sans-serif; -} - -.vertical { - width: 1px; - border-left: 1px solid #000000; - height: 100%; -} diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/files/upload-files.xqm basex-8.2.3/basex-api/src/main/webapp/dba/files/upload-files.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/files/upload-files.xqm 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/files/upload-files.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,31 @@ +(:~ + : Upload files. + : + : @author Christian Grün, BaseX GmbH, 2014-15 + :) +module namespace _ = 'dba/files'; + +import module namespace cons = 'dba/cons' at '../modules/cons.xqm'; + +(:~ Top category :) +declare variable $_:CAT := 'files'; + +(:~ + : Deletes files. + : @param $files map with uploaded files + :) +declare + %rest:POST + %rest:path("/dba/upload-files") + %rest:form-param("files", "{$files}") + %output:method("html") +function _:drop( + $files as map(*) +) { + cons:check(), + + map:for-each($files, function($name, $content) { + file:write-binary($cons:DBA-DIR || file:name($name), $content) + }), + web:redirect($_:CAT, map { 'info': 'Uploaded files: ' || map:size($files) }) +}; diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/login.xqm basex-8.2.3/basex-api/src/main/webapp/dba/login.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/login.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/login.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -18,7 +18,7 @@ : @return page :) declare - %rest:path("dba/login") + %rest:path("/dba/login") %rest:query-param("name" , "{$name}") %rest:query-param("url", "{$url}") %rest:query-param("error", "{$error}") @@ -42,13 +42,13 @@ { html:focus('user') } - { html:button('login', 'Login') } Password: + { html:button('login', 'Login') } @@ -82,7 +82,7 @@ : @return redirect :) declare - %rest:path("dba/login-check") + %rest:path("/dba/login-check") %rest:query-param("name", "{$name}") %rest:query-param("pass", "{$pass}") %rest:query-param("url", "{$url}") @@ -135,7 +135,7 @@ : Ends a session and redirects to the login page. : @return redirect :) -declare %rest:path("dba/logout") function _:logout( +declare %rest:path("/dba/logout") function _:logout( ) as element(rest:response) { let $name := $cons:SESSION/name let $url := string-join($cons:SESSION/(host, port), ':') @@ -143,7 +143,7 @@ admin:write-log('User was logged out: ' || $name), Session:delete($cons:SESSION-KEY), Session:close(), - web:redirect('login', map { 'nane': $name, 'url': $url }) + web:redirect("/dba/login", map { 'nane': $name, 'url': $url }) ) }; @@ -167,7 +167,7 @@ } ), admin:write-log('User was logged in: ' || $name), - web:redirect('databases') + web:redirect("databases") }; (:~ @@ -183,5 +183,5 @@ $message as xs:string ) as element(rest:response) { admin:write-log('Login was denied: ' || $name), - web:redirect('login', map { 'name': $name, 'url': $url, 'error': $message }) + web:redirect("login", map { 'name': $name, 'url': $url, 'error': $message }) }; diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/logs/delete-logs.xqm basex-8.2.3/basex-api/src/main/webapp/dba/logs/delete-logs.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/logs/delete-logs.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/logs/delete-logs.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -17,7 +17,7 @@ :) declare %rest:GET - %rest:path("dba/delete-logs") + %rest:path("/dba/delete-logs") %rest:query-param("name", "{$names}") %output:method("html") function _:drop( diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/logs/logs.xqm basex-8.2.3/basex-api/src/main/webapp/dba/logs/logs.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/logs/logs.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/logs/logs.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -25,7 +25,7 @@ :) declare %rest:GET - %rest:path("dba/logs") + %rest:path("/dba/logs") %rest:query-param("sort", "{$sort}", "") %rest:query-param("name", "{$name}") %rest:query-param("loglist", "{$loglist}") @@ -87,7 +87,7 @@ :) declare %rest:POST - %rest:path("dba/log") + %rest:path("/dba/log") %rest:query-param("name", "{$name}") %rest:query-param("sort", "{$sort}") %rest:query-param("loglist", "{$loglist}") @@ -130,7 +130,7 @@ :) declare %rest:POST - %rest:path("dba/loglist") + %rest:path("/dba/loglist") %rest:query-param("sort", "{$sort}") %rest:query-param("query", "{$query}") %output:method("html") @@ -172,7 +172,7 @@ :) declare %rest:POST - %rest:path("dba/logs") + %rest:path("/dba/logs") %rest:query-param("action", "{$action}") %rest:query-param("name", "{$names}") %output:method("html") diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/modules/cons.xqm basex-8.2.3/basex-api/src/main/webapp/dba/modules/cons.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/modules/cons.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/modules/cons.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -15,48 +15,48 @@ (:~ Current session. :) declare variable $cons:SESSION := Session:get($cons:SESSION-KEY); +(:~ Directory for DBA files. :) +declare variable $cons:DBA-DIR := file:resolve-path(file:temp-dir() || 'dba/') ! + (file:create-dir(.), .); (:~ Configuration file. :) -declare variable $cons:CONFIG-XML := file:base-dir() || '../files/config.xml'; -(:~ Configuration. :) -declare %private variable $cons:CONFIG := map:merge( - doc($cons:CONFIG-XML)/config/* ! map { name(): string() } -); +declare variable $cons:DBA-SETTINGS-FILE := $cons:DBA-DIR || 'dba-settings.xml'; -(:~ Language. :) -declare variable $cons:LANGUAGE := cons:string('language'); +(:~ Permissions. :) +declare variable $cons:PERMISSIONS := ('none', 'read', 'write', 'create', 'admin'); -(:~ Maximum length of XML characters (currently: 1mb). :) -declare variable $cons:MAX-CHARS := cons:integer('maxchars'); -(:~ Maximum number of table entries (currently: 1000 rows). :) -declare variable $cons:MAX-ROWS := cons:integer('maxrows'); +(:~ Maximum length of XML characters. :) +declare variable $cons:K-MAX-CHARS := 'maxchars'; +(:~ Maximum number of table entries. :) +declare variable $cons:K-MAX-ROWS := 'maxrows'; (:~ Query timeout. :) -declare variable $cons:TIMEOUT := cons:integer('timeout'); +declare variable $cons:K-TIMEOUT := 'timeout'; (:~ Maximal memory consumption. :) -declare variable $cons:MEMORY := cons:integer('memory'); +declare variable $cons:K-MEMORY := 'memory'; (:~ Permission when running queries. :) -declare variable $cons:PERMISSION := cons:string('permission'); - -(:~ Permissions. :) -declare variable $cons:PERMISSIONS := ('none', 'read', 'write', 'create', 'admin'); - -(:~ - : Returns a configuration string for the specified key. - : @param $key key - : @return text - :) -declare %private function cons:string($key as xs:string) as xs:string { - let $text := $cons:CONFIG($key) - return if($text) then $text else error((), 'Missing in config.xml: "' || $text || '"') -}; +declare variable $cons:K-PERMISSION := 'permission'; -(:~ - : Returns a configuration number for the specified key. - : @param $key key - : @return text - :) -declare %private function cons:integer($key as xs:string) as xs:integer { - xs:integer(cons:string($key)) -}; +(:~ Configuration. :) +declare variable $cons:OPTION := + let $defaults := map { + 'maxchars': 100000, + 'maxrows': 500, + 'timeout': 5, + 'memory': 100, + 'permission': 'admin' + } + return try { + (: merge defaults with options in settings file :) + map:merge(( + $defaults, + for $opt in doc($cons:DBA-SETTINGS-FILE)/config/* + let $key := $opt/name() + let $val := if($defaults($key) instance of xs:integer) then xs:integer($opt) else string($opt) + return map { $key: $val } + )) + } catch * { + (: settings file does not exist or is not well-formed... use defaults :) + $defaults + }; (:~ : Checks if the current client is logged in. If not, raises an error. diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/modules/html.xqm basex-8.2.3/basex-api/src/main/webapp/dba/modules/html.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/modules/html.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/modules/html.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -5,11 +5,10 @@ :) module namespace html = 'dba/html'; -import module namespace Request = 'http://exquery.org/ns/request'; import module namespace cons = 'dba/cons' at '../modules/cons.xqm'; (: Number formats. :) -declare variable $html:NUMBER := ('decimal','number', 'bytes'); +declare variable $html:NUMBER := ('decimal', 'number', 'bytes'); (:~ : Creates a checkbox. @@ -213,7 +212,7 @@ if(empty($sort) or $name = $sort) then ( $value ) else ( - html:link($value, Request:path(), map:merge(($param, map { 'sort': $name }))) + html:link($value, "", map:merge(($param, map { 'sort': $name }))) ) } } @@ -249,8 +248,9 @@ return $entry ) + let $max := $cons:OPTION($cons:K-MAX-ROWS) for $entry at $c in $entries - return if($c <= $cons:MAX-ROWS) then ( + return if($c <= $max) then ( { for $header at $pos in $headers let $name := $header/name() @@ -281,7 +281,7 @@ ) } } - ) else if($c = $cons:MAX-ROWS + 1) then ( + ) else if($c = $max + 1) then ( { if($buttons) then else () diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/modules/tmpl.xqm basex-8.2.3/basex-api/src/main/webapp/dba/modules/tmpl.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/modules/tmpl.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/modules/tmpl.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -40,11 +40,13 @@ Database Administration - - ) @@ -76,8 +99,8 @@ : @return result of query :) declare - %rest:path("dba/eval-query") - %rest:query-param("query", "{$query}") + %rest:POST("{$query}") + %rest:path("/dba/eval-query") %output:method("text") function _:eval-query( $query as xs:string? @@ -87,12 +110,65 @@ }; (:~ + : Returns the contents of a query file. + : @param $name name of query file (without suffix) + : @return query string + :) +declare + %rest:path("/dba/open-query") + %rest:query-param("name", "{$name}") + %output:method("text") +function _:open-query( + $name as xs:string +) as xs:string { + cons:check(), + file:read-text(_:to-path($name)) +}; + +(:~ + : Delete a query file and returns the names of stored queries. + : @param $name name of query file (without suffix) + : @return names of stored queries + :) +declare + %rest:path("/dba/delete-query") + %rest:query-param("name", "{$name}") + %output:method("text") +function _:delete-query( + $name as xs:string +) as xs:string { + cons:check(), + file:delete(_:to-path($name)), + string-join(_:files(), '/') +}; + +(:~ + : Saves a query file and returns the list of stored queries. + : @param $name name of query file (without suffix) + : @param $query query string + : @return names of stored queries + :) +declare + %rest:POST("{$query}") + %rest:path("/dba/save-query") + %rest:query-param("name", "{$name}") + %output:method("text") +function _:save-query( + $name as xs:string, + $query as xs:string +) as xs:string { + cons:check(), + file:write-text(_:to-path($name), $query), + string-join(_:files(), '/') +}; + +(:~ : Runs an updating query. : @param $query query :) declare %updating - %rest:path("dba/update-query") + %rest:path("/dba/update-query") %rest:query-param("query", "{$query}") %output:method("text") function _:update-query( @@ -106,8 +182,28 @@ : Returns the options for evaluating a query. : @return options :) -declare %private function _:query-options() { - "map { 'timeout':" || $cons:TIMEOUT || - ",'memory':" || $cons:MEMORY || - ",'permission':'" || $cons:PERMISSION || "' }" +declare %private function _:query-options() as xs:string { + "map { 'timeout':" || $cons:OPTION($cons:K-TIMEOUT) || + ",'memory':" || $cons:OPTION($cons:K-MEMORY) || + ",'permission':'" || $cons:OPTION($cons:K-PERMISSION) || "' }" +}; + +(:~ + : Returns a normalized file path for the specified file name. + : @param $name file name + : @return file path + :) +declare %private function _:to-path( + $name as xs:string +) as xs:string { + $cons:DBA-DIR || translate($name, '\/:*?"<>|', '---------') || $_:SUFFIX +}; + +(:~ + : Returns the names of all files. + : @return list of files + :) +declare %private function _:files() as xs:string* { + for $file in file:list($cons:DBA-DIR, false(), '*' || $_:SUFFIX) + return replace($file, $_:SUFFIX || '$', '') }; diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/settings/settings.xqm basex-8.2.3/basex-api/src/main/webapp/dba/settings/settings.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/settings/settings.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/settings/settings.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -9,7 +9,7 @@ import module namespace cons = 'dba/cons' at '../modules/cons.xqm'; import module namespace tmpl = 'dba/tmpl' at '../modules/tmpl.xqm'; -declare option query:write-lock "settings"; +declare option query:write-lock 'settings'; (:~ Top category :) declare variable $_:CAT := 'settings'; @@ -19,7 +19,7 @@ :) declare %rest:GET - %rest:path("dba/settings") + %rest:path("/dba/settings") %output:method("html") function _:settings( ) as element() { @@ -34,48 +34,30 @@

Querying

- - TIMEOUT: - -   - …query timeout (seconds) - - - - - MEMORY: - -   - …memory limit (mb) during query execution - - - - - MAXCHARS: - -   - …maximum number of characters in query results - - - - - MAXROWS: - -   - …maximum number of displayed table rows - - - + { + for $option in element options { + element { $cons:K-TIMEOUT } { '…query timeout (seconds)' }, + element { $cons:K-MEMORY } { '…memory limit (mb) during query execution' }, + element { $cons:K-MAX-CHARS } { '…maximum number of characters in query results' }, + element { $cons:K-MAX-ROWS } { '…maximum number of displayed table rows' } + }/* + let $key := name($option) + return + { upper-case($key) }: + +   { $option/text() } + + + } PERMISSION: -   - …for running queries - +   …for running queries @@ -90,20 +72,18 @@ :) declare %rest:POST - %rest:path("dba/settings") + %rest:path("/dba/settings") %output:method("html") function _:settings-save( ) { cons:check(), - let $config := doc($cons:CONFIG-XML)/config update ( + let $config := element config { for $key in Request:parameter-names() - (: skip empty values :) - for $value in Request:parameter($key)[.] - return replace value of node *[name() = $key] with $value - ) + return element { $key } { Request:parameter($key) } + } return ( - file:write($cons:CONFIG-XML, $config), + file:write($cons:DBA-SETTINGS-FILE, $config), web:redirect("settings") ) }; diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/static/basex.svg basex-8.2.3/basex-api/src/main/webapp/dba/static/basex.svg --- basex-8.1.1/basex-api/src/main/webapp/dba/static/basex.svg 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/static/basex.svg 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,78 @@ + +image/svg+xml + \ No newline at end of file diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/static/codemirror/lib/codemirror.css basex-8.2.3/basex-api/src/main/webapp/dba/static/codemirror/lib/codemirror.css --- basex-8.1.1/basex-api/src/main/webapp/dba/static/codemirror/lib/codemirror.css 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/static/codemirror/lib/codemirror.css 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,322 @@ +/* BASICS */ + +.CodeMirror { + font-family: "Consolas", "Ubuntu Mono", monospace; +} + +/* PADDING */ + +.CodeMirror-lines { + padding: 4px 0; /* Vertical padding around content */ +} +.CodeMirror pre { + padding: 0 4px; /* Horizontal padding of content */ +} + +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + background-color: white; /* The little square between H and V scrollbars */ +} + +/* GUTTER */ + +.CodeMirror-gutters { + border-right: 1px solid #ddd; + background-color: #f7f7f7; + white-space: nowrap; +} +.CodeMirror-linenumbers {} +.CodeMirror-linenumber { + padding: 0 3px 0 5px; + min-width: 20px; + text-align: right; + color: #999; + white-space: nowrap; +} + +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + +/* CURSOR */ + +.CodeMirror div.CodeMirror-cursor { + border-left: 1px solid black; +} +/* Shown when moving in bi-directional text */ +.CodeMirror div.CodeMirror-secondarycursor { + border-left: 1px solid silver; +} +.CodeMirror.cm-fat-cursor div.CodeMirror-cursor { + width: auto; + border: 0; + background: #7e7; +} +.CodeMirror.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; +} +@-moz-keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} +@-webkit-keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} +@keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} + +/* Can style cursor different in overwrite (non-insert) mode */ +div.CodeMirror-overwrite div.CodeMirror-cursor {} + +.cm-tab { display: inline-block; text-decoration: inherit; } + +.CodeMirror-ruler { + border-left: 1px solid #ccc; + position: absolute; +} + +/* DEFAULT THEME */ + +.cm-s-default .cm-keyword {color: #708;} +.cm-s-default .cm-atom {color: #219;} +.cm-s-default .cm-number {color: #164;} +.cm-s-default .cm-def {color: #00f;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} +.cm-s-default .cm-variable-2 {color: #05a;} +.cm-s-default .cm-variable-3 {color: #085;} +.cm-s-default .cm-comment {color: #a50;} +.cm-s-default .cm-string {color: #a11;} +.cm-s-default .cm-string-2 {color: #f50;} +.cm-s-default .cm-meta {color: #555;} +.cm-s-default .cm-qualifier {color: #555;} +.cm-s-default .cm-builtin {color: #30a;} +.cm-s-default .cm-bracket {color: #997;} +.cm-s-default .cm-tag {color: #170;} +.cm-s-default .cm-attribute {color: #00c;} +.cm-s-default .cm-header {color: blue;} +.cm-s-default .cm-quote {color: #090;} +.cm-s-default .cm-hr {color: #999;} +.cm-s-default .cm-link {color: #00c;} + +.cm-negative {color: #d44;} +.cm-positive {color: #292;} +.cm-header, .cm-strong {font-weight: bold;} +.cm-em {font-style: italic;} +.cm-link {text-decoration: underline;} +.cm-strikethrough {text-decoration: line-through;} + +.cm-s-default .cm-error {color: #f00;} +.cm-invalidchar {color: #f00;} + +.CodeMirror-composing { border-bottom: 2px solid; } + +/* Default styles for common addons */ + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } +.CodeMirror-activeline-background {background: #e8f2ff;} + +/* STOP */ + +/* The rest of this file contains styles related to the mechanics of + the editor. You probably shouldn't touch them. */ + +.CodeMirror { + position: relative; + overflow: hidden; + background: white; +} + +.CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ + /* 30px is the magic margin used to hide the element's real scrollbars */ + /* See overflow: hidden in .CodeMirror */ + margin-bottom: -30px; margin-right: -30px; + padding-bottom: 30px; + height: 100%; + outline: none; /* Prevent dragging from highlighting the element */ + position: relative; +} +.CodeMirror-sizer { + position: relative; + border-right: 30px solid transparent; +} + +/* The fake, visible scrollbars. Used to force redraw during scrolling + before actuall scrolling happens, thus preventing shaking and + flickering artifacts. */ +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; +} +.CodeMirror-vscrollbar { + right: 0; top: 0; + overflow-x: hidden; + overflow-y: scroll; +} +.CodeMirror-hscrollbar { + bottom: 0; left: 0; + overflow-y: hidden; + overflow-x: scroll; +} +.CodeMirror-scrollbar-filler { + right: 0; bottom: 0; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; +} + +.CodeMirror-gutters { + position: absolute; left: 0; top: 0; + z-index: 3; +} +.CodeMirror-gutter { + white-space: normal; + height: 100%; + display: inline-block; + margin-bottom: -30px; + /* Hack to make IE7 behave */ + *zoom:1; + *display:inline; +} +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + height: 100%; +} +.CodeMirror-gutter-elt { + position: absolute; + cursor: default; + z-index: 4; +} +.CodeMirror-gutter-wrapper { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.CodeMirror-lines { + cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ +} +.CodeMirror pre { + /* Reset some styles that the rest of the page might have set */ + -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + -webkit-tap-highlight-color: transparent; +} +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.CodeMirror-linebackground { + position: absolute; + left: 0; right: 0; top: 0; bottom: 0; + z-index: 0; +} + +.CodeMirror-linewidget { + position: relative; + z-index: 2; + overflow: auto; +} + +.CodeMirror-widget {} + +.CodeMirror-code { + outline: none; +} + +/* Force content-box sizing for the elements where we expect it */ +.CodeMirror-scroll, +.CodeMirror-sizer, +.CodeMirror-gutter, +.CodeMirror-gutters, +.CodeMirror-linenumber { + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} +.CodeMirror-measure pre { position: static; } + +.CodeMirror div.CodeMirror-cursor { + position: absolute; + border-right: none; + width: 0; +} + +div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} +.CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } +.CodeMirror ::selection { background: #d7d4f0; } +.CodeMirror ::-moz-selection { background: #d7d4f0; } + +.cm-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); +} + +/* IE7 hack to prevent it from returning funny offsetTops on the spans */ +.CodeMirror span { *vertical-align: text-bottom; } + +/* Used to force a border model for a node */ +.cm-force-border { padding-right: .1px; } + +@media print { + /* Hide the cursor when printing */ + .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/static/codemirror/lib/codemirror.js basex-8.2.3/basex-api/src/main/webapp/dba/static/codemirror/lib/codemirror.js --- basex-8.1.1/basex-api/src/main/webapp/dba/static/codemirror/lib/codemirror.js 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/static/codemirror/lib/codemirror.js 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,8747 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// This is CodeMirror (http://codemirror.net), a code editor +// implemented in JavaScript on top of the browser's DOM. +// +// You can find some technical background for some of the code below +// at http://marijnhaverbeke.nl/blog/#cm-internals . + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + module.exports = mod(); + else if (typeof define == "function" && define.amd) // AMD + return define([], mod); + else // Plain browser env + this.CodeMirror = mod(); +})(function() { + "use strict"; + + // BROWSER SNIFFING + + // Kludges for bugs and behavior differences that can't be feature + // detected are enabled based on userAgent etc sniffing. + + var gecko = /gecko\/\d/i.test(navigator.userAgent); + var ie_upto10 = /MSIE \d/.test(navigator.userAgent); + var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent); + var ie = ie_upto10 || ie_11up; + var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); + var webkit = /WebKit\//.test(navigator.userAgent); + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); + var chrome = /Chrome\//.test(navigator.userAgent); + var presto = /Opera\//.test(navigator.userAgent); + var safari = /Apple Computer/.test(navigator.vendor); + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); + var phantom = /PhantomJS/.test(navigator.userAgent); + + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + // This is woefully incomplete. Suggestions for alternative methods welcome. + var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); + var windows = /win/i.test(navigator.platform); + + var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/); + if (presto_version) presto_version = Number(presto_version[1]); + if (presto_version && presto_version >= 15) { presto = false; webkit = true; } + // Some browsers use the wrong event properties to signal cmd/ctrl on OS X + var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); + var captureRightClick = gecko || (ie && ie_version >= 9); + + // Optimize some code when these features are not used. + var sawReadOnlySpans = false, sawCollapsedSpans = false; + + // EDITOR CONSTRUCTOR + + // A CodeMirror instance represents an editor. This is the object + // that user code is usually dealing with. + + function CodeMirror(place, options) { + if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); + + this.options = options = options ? copyObj(options) : {}; + // Determine effective options based on given values and defaults. + copyObj(defaults, options, false); + setGuttersForLineNumbers(options); + + var doc = options.value; + if (typeof doc == "string") doc = new Doc(doc, options.mode); + this.doc = doc; + + var input = new CodeMirror.inputStyles[options.inputStyle](this); + var display = this.display = new Display(place, doc, input); + display.wrapper.CodeMirror = this; + updateGutters(this); + themeChanged(this); + if (options.lineWrapping) + this.display.wrapper.className += " CodeMirror-wrap"; + if (options.autofocus && !mobile) display.input.focus(); + initScrollbars(this); + + this.state = { + keyMaps: [], // stores maps added by addKeyMap + overlays: [], // highlighting overlays, as added by addOverlay + modeGen: 0, // bumped when mode/overlay changes, used to invalidate highlighting info + overwrite: false, + delayingBlurEvent: false, + focused: false, + suppressEdits: false, // used to disable editing during key handlers when in readOnly mode + pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll + draggingText: false, + highlight: new Delayed(), // stores highlight worker timeout + keySeq: null, // Unfinished key sequence + specialChars: null + }; + + var cm = this; + + // Override magic textarea content restore that IE sometimes does + // on our hidden textarea on reload + if (ie && ie_version < 11) setTimeout(function() { cm.display.input.reset(true); }, 20); + + registerEventHandlers(this); + ensureGlobalHandlers(); + + startOperation(this); + this.curOp.forceUpdate = true; + attachDoc(this, doc); + + if ((options.autofocus && !mobile) || cm.hasFocus()) + setTimeout(bind(onFocus, this), 20); + else + onBlur(this); + + for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) + optionHandlers[opt](this, options[opt], Init); + maybeUpdateLineNumberWidth(this); + if (options.finishInit) options.finishInit(this); + for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); + endOperation(this); + // Suppress optimizelegibility in Webkit, since it breaks text + // measuring on line wrapping boundaries. + if (webkit && options.lineWrapping && + getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") + display.lineDiv.style.textRendering = "auto"; + } + + // DISPLAY CONSTRUCTOR + + // The display handles the DOM integration, both for input reading + // and content drawing. It holds references to DOM nodes and + // display-related state. + + function Display(place, doc, input) { + var d = this; + this.input = input; + + // Covers bottom-right square when both scrollbars are present. + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + d.scrollbarFiller.setAttribute("cm-not-content", "true"); + // Covers bottom of gutter when coverGutterNextToScrollbar is on + // and h scrollbar is present. + d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); + d.gutterFiller.setAttribute("cm-not-content", "true"); + // Will contain the actual code, positioned to cover the viewport. + d.lineDiv = elt("div", null, "CodeMirror-code"); + // Elements are added to these to represent selection and cursors. + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); + d.cursorDiv = elt("div", null, "CodeMirror-cursors"); + // A visibility: hidden element used to find the size of things. + d.measure = elt("div", null, "CodeMirror-measure"); + // When lines outside of the viewport are measured, they are drawn in this. + d.lineMeasure = elt("div", null, "CodeMirror-measure"); + // Wraps everything that needs to exist inside the vertically-padded coordinate system + d.lineSpace = elt("div", [d.measure, d.lineMeasure, d.selectionDiv, d.cursorDiv, d.lineDiv], + null, "position: relative; outline: none"); + // Moved around its parent to cover visible view. + d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); + // Set to the height of the document, allowing scrolling. + d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); + d.sizerWidth = null; + // Behavior of elts with overflow: auto and padding is + // inconsistent across browsers. This is used to ensure the + // scrollable area is big enough. + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); + // Will contain the gutters, if any. + d.gutters = elt("div", null, "CodeMirror-gutters"); + d.lineGutter = null; + // Actual scrollable element. + d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); + d.scroller.setAttribute("tabIndex", "-1"); + // The element in which the editor lives. + d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); + + // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } + if (!webkit && !(gecko && mobile)) d.scroller.draggable = true; + + if (place) { + if (place.appendChild) place.appendChild(d.wrapper); + else place(d.wrapper); + } + + // Current rendered range (may be bigger than the view window). + d.viewFrom = d.viewTo = doc.first; + d.reportedViewFrom = d.reportedViewTo = doc.first; + // Information about the rendered lines. + d.view = []; + d.renderedView = null; + // Holds info about a single rendered line when it was rendered + // for measurement, while not in view. + d.externalMeasured = null; + // Empty space (in pixels) above the view + d.viewOffset = 0; + d.lastWrapHeight = d.lastWrapWidth = 0; + d.updateLineNumbers = null; + + d.nativeBarWidth = d.barHeight = d.barWidth = 0; + d.scrollbarsClipped = false; + + // Used to only resize the line number gutter when necessary (when + // the amount of lines crosses a boundary that makes its width change) + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; + // Set to true when a non-horizontal-scrolling line widget is + // added. As an optimization, line widget aligning is skipped when + // this is false. + d.alignWidgets = false; + + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + + // Tracks the maximum line length so that the horizontal scrollbar + // can be kept static when scrolling. + d.maxLine = null; + d.maxLineLength = 0; + d.maxLineChanged = false; + + // Used for measuring wheel scrolling granularity + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; + + // True when shift is held down. + d.shift = false; + + // Used to track whether anything happened since the context menu + // was opened. + d.selForContextMenu = null; + + d.activeTouch = null; + + input.init(d); + } + + // STATE UPDATES + + // Used to get the editor into a consistent state again when options change. + + function loadMode(cm) { + cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); + resetModeState(cm); + } + + function resetModeState(cm) { + cm.doc.iter(function(line) { + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + }); + cm.doc.frontier = cm.doc.first; + startWorker(cm, 100); + cm.state.modeGen++; + if (cm.curOp) regChange(cm); + } + + function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap"); + cm.display.sizer.style.minWidth = ""; + cm.display.sizerWidth = null; + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap"); + findMaxLine(cm); + } + estimateLineHeights(cm); + regChange(cm); + clearCaches(cm); + setTimeout(function(){updateScrollbars(cm);}, 100); + } + + // Returns a function that estimates the height of a line, to use as + // first approximation until the line becomes visible (and is thus + // properly measurable). + function estimateHeight(cm) { + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); + return function(line) { + if (lineIsHidden(cm.doc, line)) return 0; + + var widgetsHeight = 0; + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) { + if (line.widgets[i].height) widgetsHeight += line.widgets[i].height; + } + + if (wrapping) + return widgetsHeight + (Math.ceil(line.text.length / perLine) || 1) * th; + else + return widgetsHeight + th; + }; + } + + function estimateLineHeights(cm) { + var doc = cm.doc, est = estimateHeight(cm); + doc.iter(function(line) { + var estHeight = est(line); + if (estHeight != line.height) updateLineHeight(line, estHeight); + }); + } + + function themeChanged(cm) { + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + clearCaches(cm); + } + + function guttersChanged(cm) { + updateGutters(cm); + regChange(cm); + setTimeout(function(){alignHorizontally(cm);}, 20); + } + + // Rebuild the gutter elements, ensure the margin to the left of the + // code matches their width. + function updateGutters(cm) { + var gutters = cm.display.gutters, specs = cm.options.gutters; + removeChildren(gutters); + for (var i = 0; i < specs.length; ++i) { + var gutterClass = specs[i]; + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); + if (gutterClass == "CodeMirror-linenumbers") { + cm.display.lineGutter = gElt; + gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; + } + } + gutters.style.display = i ? "" : "none"; + updateGutterSpace(cm); + } + + function updateGutterSpace(cm) { + var width = cm.display.gutters.offsetWidth; + cm.display.sizer.style.marginLeft = width + "px"; + } + + // Compute the character length of a line, taking into account + // collapsed ranges (see markText) that might hide parts, and join + // other lines onto it. + function lineLength(line) { + if (line.height == 0) return 0; + var len = line.text.length, merged, cur = line; + while (merged = collapsedSpanAtStart(cur)) { + var found = merged.find(0, true); + cur = found.from.line; + len += found.from.ch - found.to.ch; + } + cur = line; + while (merged = collapsedSpanAtEnd(cur)) { + var found = merged.find(0, true); + len -= cur.text.length - found.from.ch; + cur = found.to.line; + len += cur.text.length - found.to.ch; + } + return len; + } + + // Find the longest line in the document. + function findMaxLine(cm) { + var d = cm.display, doc = cm.doc; + d.maxLine = getLine(doc, doc.first); + d.maxLineLength = lineLength(d.maxLine); + d.maxLineChanged = true; + doc.iter(function(line) { + var len = lineLength(line); + if (len > d.maxLineLength) { + d.maxLineLength = len; + d.maxLine = line; + } + }); + } + + // Make sure the gutters options contains the element + // "CodeMirror-linenumbers" when the lineNumbers option is true. + function setGuttersForLineNumbers(options) { + var found = indexOf(options.gutters, "CodeMirror-linenumbers"); + if (found == -1 && options.lineNumbers) { + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); + } else if (found > -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0); + options.gutters.splice(found, 1); + } + } + + // SCROLLBARS + + // Prepare DOM reads needed to update the scrollbars. Done in one + // shot to minimize update/measure roundtrips. + function measureForScrollbars(cm) { + var d = cm.display, gutterW = d.gutters.offsetWidth; + var docH = Math.round(cm.doc.height + paddingVert(cm.display)); + return { + clientHeight: d.scroller.clientHeight, + viewHeight: d.wrapper.clientHeight, + scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, + viewWidth: d.wrapper.clientWidth, + barLeft: cm.options.fixedGutter ? gutterW : 0, + docHeight: docH, + scrollHeight: docH + scrollGap(cm) + d.barHeight, + nativeBarWidth: d.nativeBarWidth, + gutterWidth: gutterW + }; + } + + function NativeScrollbars(place, scroll, cm) { + this.cm = cm; + var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); + var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); + place(vert); place(horiz); + + on(vert, "scroll", function() { + if (vert.clientHeight) scroll(vert.scrollTop, "vertical"); + }); + on(horiz, "scroll", function() { + if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal"); + }); + + this.checkedOverlay = false; + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; + } + + NativeScrollbars.prototype = copyObj({ + update: function(measure) { + var needsH = measure.scrollWidth > measure.clientWidth + 1; + var needsV = measure.scrollHeight > measure.clientHeight + 1; + var sWidth = measure.nativeBarWidth; + + if (needsV) { + this.vert.style.display = "block"; + this.vert.style.bottom = needsH ? sWidth + "px" : "0"; + var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); + // A bug in IE8 can cause this value to be negative, so guard it. + this.vert.firstChild.style.height = + Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; + } else { + this.vert.style.display = ""; + this.vert.firstChild.style.height = "0"; + } + + if (needsH) { + this.horiz.style.display = "block"; + this.horiz.style.right = needsV ? sWidth + "px" : "0"; + this.horiz.style.left = measure.barLeft + "px"; + var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); + this.horiz.firstChild.style.width = + (measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; + } else { + this.horiz.style.display = ""; + this.horiz.firstChild.style.width = "0"; + } + + if (!this.checkedOverlay && measure.clientHeight > 0) { + if (sWidth == 0) this.overlayHack(); + this.checkedOverlay = true; + } + + return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}; + }, + setScrollLeft: function(pos) { + if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos; + }, + setScrollTop: function(pos) { + if (this.vert.scrollTop != pos) this.vert.scrollTop = pos; + }, + overlayHack: function() { + var w = mac && !mac_geMountainLion ? "12px" : "18px"; + this.horiz.style.minHeight = this.vert.style.minWidth = w; + var self = this; + var barMouseDown = function(e) { + if (e_target(e) != self.vert && e_target(e) != self.horiz) + operation(self.cm, onMouseDown)(e); + }; + on(this.vert, "mousedown", barMouseDown); + on(this.horiz, "mousedown", barMouseDown); + }, + clear: function() { + var parent = this.horiz.parentNode; + parent.removeChild(this.horiz); + parent.removeChild(this.vert); + } + }, NativeScrollbars.prototype); + + function NullScrollbars() {} + + NullScrollbars.prototype = copyObj({ + update: function() { return {bottom: 0, right: 0}; }, + setScrollLeft: function() {}, + setScrollTop: function() {}, + clear: function() {} + }, NullScrollbars.prototype); + + CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}; + + function initScrollbars(cm) { + if (cm.display.scrollbars) { + cm.display.scrollbars.clear(); + if (cm.display.scrollbars.addClass) + rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); + } + + cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarStyle](function(node) { + cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); + // Prevent clicks in the scrollbars from killing focus + on(node, "mousedown", function() { + if (cm.state.focused) setTimeout(function() { cm.display.input.focus(); }, 0); + }); + node.setAttribute("cm-not-content", "true"); + }, function(pos, axis) { + if (axis == "horizontal") setScrollLeft(cm, pos); + else setScrollTop(cm, pos); + }, cm); + if (cm.display.scrollbars.addClass) + addClass(cm.display.wrapper, cm.display.scrollbars.addClass); + } + + function updateScrollbars(cm, measure) { + if (!measure) measure = measureForScrollbars(cm); + var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; + updateScrollbarsInner(cm, measure); + for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { + if (startWidth != cm.display.barWidth && cm.options.lineWrapping) + updateHeightsInViewport(cm); + updateScrollbarsInner(cm, measureForScrollbars(cm)); + startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; + } + } + + // Re-synchronize the fake scrollbars with the actual size of the + // content. + function updateScrollbarsInner(cm, measure) { + var d = cm.display; + var sizes = d.scrollbars.update(measure); + + d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; + d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; + + if (sizes.right && sizes.bottom) { + d.scrollbarFiller.style.display = "block"; + d.scrollbarFiller.style.height = sizes.bottom + "px"; + d.scrollbarFiller.style.width = sizes.right + "px"; + } else d.scrollbarFiller.style.display = ""; + if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = sizes.bottom + "px"; + d.gutterFiller.style.width = measure.gutterWidth + "px"; + } else d.gutterFiller.style.display = ""; + } + + // Compute the lines that are visible in a given viewport (defaults + // the the current scroll position). viewport may contain top, + // height, and ensure (see op.scrollToPos) properties. + function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; + top = Math.floor(top - paddingTop(display)); + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; + + var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); + // Ensure is a {from: {line, ch}, to: {line, ch}} object, and + // forces those lines into the viewport (if possible). + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; + if (ensureFrom < from) { + from = ensureFrom; + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); + } else if (Math.min(ensureTo, doc.lastLine()) >= to) { + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); + to = ensureTo; + } + } + return {from: from, to: Math.max(to, from + 1)}; + } + + // LINE NUMBERS + + // Re-align line numbers and gutter marks to compensate for + // horizontal scrolling. + function alignHorizontally(cm) { + var display = cm.display, view = display.view; + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; + var gutterW = display.gutters.offsetWidth, left = comp + "px"; + for (var i = 0; i < view.length; i++) if (!view[i].hidden) { + if (cm.options.fixedGutter && view[i].gutter) + view[i].gutter.style.left = left; + var align = view[i].alignable; + if (align) for (var j = 0; j < align.length; j++) + align[j].style.left = left; + } + if (cm.options.fixedGutter) + display.gutters.style.left = (comp + gutterW) + "px"; + } + + // Used to ensure that the line number gutter is still the right + // size for the current document size. Returns true when an update + // is needed. + function maybeUpdateLineNumberWidth(cm) { + if (!cm.options.lineNumbers) return false; + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; + if (last.length != display.lineNumChars) { + var test = display.measure.appendChild(elt("div", [elt("div", last)], + "CodeMirror-linenumber CodeMirror-gutter-elt")); + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; + display.lineGutter.style.width = ""; + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding) + 1; + display.lineNumWidth = display.lineNumInnerWidth + padding; + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; + display.lineGutter.style.width = display.lineNumWidth + "px"; + updateGutterSpace(cm); + return true; + } + return false; + } + + function lineNumberFor(options, i) { + return String(options.lineNumberFormatter(i + options.firstLineNumber)); + } + + // Computes display.scroller.scrollLeft + display.gutters.offsetWidth, + // but using getBoundingClientRect to get a sub-pixel-accurate + // result. + function compensateForHScroll(display) { + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left; + } + + // DISPLAY DRAWING + + function DisplayUpdate(cm, viewport, force) { + var display = cm.display; + + this.viewport = viewport; + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport); + this.editorIsHidden = !display.wrapper.offsetWidth; + this.wrapperHeight = display.wrapper.clientHeight; + this.wrapperWidth = display.wrapper.clientWidth; + this.oldDisplayWidth = displayWidth(cm); + this.force = force; + this.dims = getDimensions(cm); + this.events = []; + } + + DisplayUpdate.prototype.signal = function(emitter, type) { + if (hasHandler(emitter, type)) + this.events.push(arguments); + }; + DisplayUpdate.prototype.finish = function() { + for (var i = 0; i < this.events.length; i++) + signal.apply(null, this.events[i]); + }; + + function maybeClipScrollbars(cm) { + var display = cm.display; + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; + display.heightForcer.style.height = scrollGap(cm) + "px"; + display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; + display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; + display.scrollbarsClipped = true; + } + } + + // Does the actual updating of the line display. Bails out + // (returning false) when there is nothing to be done and forced is + // false. + function updateDisplayIfNeeded(cm, update) { + var display = cm.display, doc = cm.doc; + + if (update.editorIsHidden) { + resetView(cm); + return false; + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + display.renderedView == display.view && countDirtyView(cm) == 0) + return false; + + if (maybeUpdateLineNumberWidth(cm)) { + resetView(cm); + update.dims = getDimensions(cm); + } + + // Compute a suitable new viewport (from & to) + var end = doc.first + doc.size; + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); + var to = Math.min(end, update.visible.to + cm.options.viewportMargin); + if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom); + if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo); + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from); + to = visualLineEndNo(cm.doc, to); + } + + var different = from != display.viewFrom || to != display.viewTo || + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; + adjustView(cm, from, to); + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px"; + + var toUpdate = countDirtyView(cm); + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + return false; + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + var focused = activeElt(); + if (toUpdate > 4) display.lineDiv.style.display = "none"; + patchDisplay(cm, display.updateLineNumbers, update.dims); + if (toUpdate > 4) display.lineDiv.style.display = ""; + display.renderedView = display.view; + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + if (focused && activeElt() != focused && focused.offsetHeight) focused.focus(); + + // Prevent selection and cursors from interfering with the scroll + // width and height. + removeChildren(display.cursorDiv); + removeChildren(display.selectionDiv); + display.gutters.style.height = 0; + + if (different) { + display.lastWrapHeight = update.wrapperHeight; + display.lastWrapWidth = update.wrapperWidth; + startWorker(cm, 400); + } + + display.updateLineNumbers = null; + + return true; + } + + function postUpdateDisplay(cm, update) { + var viewport = update.viewport; + for (var first = true;; first = false) { + if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport); + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + break; + } + if (!updateDisplayIfNeeded(cm, update)) break; + updateHeightsInViewport(cm); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + } + + update.signal(cm, "update", cm); + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; + } + } + + function updateDisplaySimple(cm, viewport) { + var update = new DisplayUpdate(cm, viewport); + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm); + postUpdateDisplay(cm, update); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + update.finish(); + } + } + + function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = measure.docHeight + "px"; + var total = measure.docHeight + cm.display.barHeight; + cm.display.heightForcer.style.top = total + "px"; + cm.display.gutters.style.height = Math.max(total + scrollGap(cm), measure.clientHeight) + "px"; + } + + // Read the actual heights of the rendered lines, and update their + // stored heights to match. + function updateHeightsInViewport(cm) { + var display = cm.display; + var prevBottom = display.lineDiv.offsetTop; + for (var i = 0; i < display.view.length; i++) { + var cur = display.view[i], height; + if (cur.hidden) continue; + if (ie && ie_version < 8) { + var bot = cur.node.offsetTop + cur.node.offsetHeight; + height = bot - prevBottom; + prevBottom = bot; + } else { + var box = cur.node.getBoundingClientRect(); + height = box.bottom - box.top; + } + var diff = cur.line.height - height; + if (height < 2) height = textHeight(display); + if (diff > .001 || diff < -.001) { + updateLineHeight(cur.line, height); + updateWidgetHeight(cur.line); + if (cur.rest) for (var j = 0; j < cur.rest.length; j++) + updateWidgetHeight(cur.rest[j]); + } + } + } + + // Read and store the height of line widgets associated with the + // given line. + function updateWidgetHeight(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; ++i) + line.widgets[i].height = line.widgets[i].node.offsetHeight; + } + + // Do a bulk-read of the DOM positions and sizes needed to draw the + // view, so that we don't interleave reading and writing to the DOM. + function getDimensions(cm) { + var d = cm.display, left = {}, width = {}; + var gutterLeft = d.gutters.clientLeft; + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { + left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; + width[cm.options.gutters[i]] = n.clientWidth; + } + return {fixedPos: compensateForHScroll(d), + gutterTotalWidth: d.gutters.offsetWidth, + gutterLeft: left, + gutterWidth: width, + wrapperWidth: d.wrapper.clientWidth}; + } + + // Sync the actual display DOM structure with display.view, removing + // nodes for lines that are no longer in view, and creating the ones + // that are not there yet, and updating the ones that are out of + // date. + function patchDisplay(cm, updateNumbersFrom, dims) { + var display = cm.display, lineNumbers = cm.options.lineNumbers; + var container = display.lineDiv, cur = container.firstChild; + + function rm(node) { + var next = node.nextSibling; + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + node.style.display = "none"; + else + node.parentNode.removeChild(node); + return next; + } + + var view = display.view, lineN = display.viewFrom; + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (lineView.hidden) { + } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet + var node = buildLineElement(cm, lineView, lineN, dims); + container.insertBefore(node, cur); + } else { // Already drawn + while (cur != lineView.node) cur = rm(cur); + var updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber; + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false; + updateLineForChanges(cm, lineView, lineN, dims); + } + if (updateNumber) { + removeChildren(lineView.lineNumber); + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))); + } + cur = lineView.node.nextSibling; + } + lineN += lineView.size; + } + while (cur) cur = rm(cur); + } + + // When an aspect of a line changes, a string is added to + // lineView.changes. This updates the relevant part of the line's + // DOM structure. + function updateLineForChanges(cm, lineView, lineN, dims) { + for (var j = 0; j < lineView.changes.length; j++) { + var type = lineView.changes[j]; + if (type == "text") updateLineText(cm, lineView); + else if (type == "gutter") updateLineGutter(cm, lineView, lineN, dims); + else if (type == "class") updateLineClasses(lineView); + else if (type == "widget") updateLineWidgets(cm, lineView, dims); + } + lineView.changes = null; + } + + // Lines with gutter elements, widgets or a background class need to + // be wrapped, and have the extra elements added to the wrapper div + function ensureLineWrapped(lineView) { + if (lineView.node == lineView.text) { + lineView.node = elt("div", null, null, "position: relative"); + if (lineView.text.parentNode) + lineView.text.parentNode.replaceChild(lineView.node, lineView.text); + lineView.node.appendChild(lineView.text); + if (ie && ie_version < 8) lineView.node.style.zIndex = 2; + } + return lineView.node; + } + + function updateLineBackground(lineView) { + var cls = lineView.bgClass ? lineView.bgClass + " " + (lineView.line.bgClass || "") : lineView.line.bgClass; + if (cls) cls += " CodeMirror-linebackground"; + if (lineView.background) { + if (cls) lineView.background.className = cls; + else { lineView.background.parentNode.removeChild(lineView.background); lineView.background = null; } + } else if (cls) { + var wrap = ensureLineWrapped(lineView); + lineView.background = wrap.insertBefore(elt("div", null, cls), wrap.firstChild); + } + } + + // Wrapper around buildLineContent which will reuse the structure + // in display.externalMeasured when possible. + function getLineContent(cm, lineView) { + var ext = cm.display.externalMeasured; + if (ext && ext.line == lineView.line) { + cm.display.externalMeasured = null; + lineView.measure = ext.measure; + return ext.built; + } + return buildLineContent(cm, lineView); + } + + // Redraw the line's text. Interacts with the background and text + // classes because the mode may output tokens that influence these + // classes. + function updateLineText(cm, lineView) { + var cls = lineView.text.className; + var built = getLineContent(cm, lineView); + if (lineView.text == lineView.node) lineView.node = built.pre; + lineView.text.parentNode.replaceChild(built.pre, lineView.text); + lineView.text = built.pre; + if (built.bgClass != lineView.bgClass || built.textClass != lineView.textClass) { + lineView.bgClass = built.bgClass; + lineView.textClass = built.textClass; + updateLineClasses(lineView); + } else if (cls) { + lineView.text.className = cls; + } + } + + function updateLineClasses(lineView) { + updateLineBackground(lineView); + if (lineView.line.wrapClass) + ensureLineWrapped(lineView).className = lineView.line.wrapClass; + else if (lineView.node != lineView.text) + lineView.node.className = ""; + var textClass = lineView.textClass ? lineView.textClass + " " + (lineView.line.textClass || "") : lineView.line.textClass; + lineView.text.className = textClass || ""; + } + + function updateLineGutter(cm, lineView, lineN, dims) { + if (lineView.gutter) { + lineView.node.removeChild(lineView.gutter); + lineView.gutter = null; + } + var markers = lineView.line.gutterMarkers; + if (cm.options.lineNumbers || markers) { + var wrap = ensureLineWrapped(lineView); + var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " + + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + + "px; width: " + dims.gutterTotalWidth + "px"); + cm.display.input.setUneditable(gutterWrap); + wrap.insertBefore(gutterWrap, lineView.text); + if (lineView.line.gutterClass) + gutterWrap.className += " " + lineView.line.gutterClass; + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) + lineView.lineNumber = gutterWrap.appendChild( + elt("div", lineNumberFor(cm.options, lineN), + "CodeMirror-linenumber CodeMirror-gutter-elt", + "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " + + cm.display.lineNumInnerWidth + "px")); + if (markers) for (var k = 0; k < cm.options.gutters.length; ++k) { + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; + if (found) + gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + + dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); + } + } + } + + function updateLineWidgets(cm, lineView, dims) { + if (lineView.alignable) lineView.alignable = null; + for (var node = lineView.node.firstChild, next; node; node = next) { + var next = node.nextSibling; + if (node.className == "CodeMirror-linewidget") + lineView.node.removeChild(node); + } + insertLineWidgets(cm, lineView, dims); + } + + // Build a line's DOM representation from scratch + function buildLineElement(cm, lineView, lineN, dims) { + var built = getLineContent(cm, lineView); + lineView.text = lineView.node = built.pre; + if (built.bgClass) lineView.bgClass = built.bgClass; + if (built.textClass) lineView.textClass = built.textClass; + + updateLineClasses(lineView); + updateLineGutter(cm, lineView, lineN, dims); + insertLineWidgets(cm, lineView, dims); + return lineView.node; + } + + // A lineView may contain multiple logical lines (when merged by + // collapsed spans). The widgets for all of them need to be drawn. + function insertLineWidgets(cm, lineView, dims) { + insertLineWidgetsFor(cm, lineView.line, lineView, dims, true); + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + insertLineWidgetsFor(cm, lineView.rest[i], lineView, dims, false); + } + + function insertLineWidgetsFor(cm, line, lineView, dims, allowAbove) { + if (!line.widgets) return; + var wrap = ensureLineWrapped(lineView); + for (var i = 0, ws = line.widgets; i < ws.length; ++i) { + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); + if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true"); + positionLineWidget(widget, node, lineView, dims); + cm.display.input.setUneditable(node); + if (allowAbove && widget.above) + wrap.insertBefore(node, lineView.gutter || lineView.text); + else + wrap.appendChild(node); + signalLater(widget, "redraw"); + } + } + + function positionLineWidget(widget, node, lineView, dims) { + if (widget.noHScroll) { + (lineView.alignable || (lineView.alignable = [])).push(node); + var width = dims.wrapperWidth; + node.style.left = dims.fixedPos + "px"; + if (!widget.coverGutter) { + width -= dims.gutterTotalWidth; + node.style.paddingLeft = dims.gutterTotalWidth + "px"; + } + node.style.width = width + "px"; + } + if (widget.coverGutter) { + node.style.zIndex = 5; + node.style.position = "relative"; + if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; + } + } + + // POSITION OBJECT + + // A Pos instance represents a position within the text. + var Pos = CodeMirror.Pos = function(line, ch) { + if (!(this instanceof Pos)) return new Pos(line, ch); + this.line = line; this.ch = ch; + }; + + // Compare two positions, return 0 if they are the same, a negative + // number when a is less, and a positive number otherwise. + var cmp = CodeMirror.cmpPos = function(a, b) { return a.line - b.line || a.ch - b.ch; }; + + function copyPos(x) {return Pos(x.line, x.ch);} + function maxPos(a, b) { return cmp(a, b) < 0 ? b : a; } + function minPos(a, b) { return cmp(a, b) < 0 ? a : b; } + + // INPUT HANDLING + + function ensureFocus(cm) { + if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); } + } + + function isReadOnly(cm) { + return cm.options.readOnly || cm.doc.cantEdit; + } + + // This will be set to an array of strings when copying, so that, + // when pasting, we know what kind of selections the copied text + // was made out of. + var lastCopied = null; + + function applyTextInput(cm, inserted, deleted, sel, origin) { + var doc = cm.doc; + cm.display.shift = false; + if (!sel) sel = doc.sel; + + var textLines = splitLines(inserted), multiPaste = null; + // When pasing N lines into N selections, insert one line per selection + if (cm.state.pasteIncoming && sel.ranges.length > 1) { + if (lastCopied && lastCopied.join("\n") == inserted) + multiPaste = sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines); + else if (textLines.length == sel.ranges.length) + multiPaste = map(textLines, function(l) { return [l]; }); + } + + // Normal behavior is to insert the new text into every selection + for (var i = sel.ranges.length - 1; i >= 0; i--) { + var range = sel.ranges[i]; + var from = range.from(), to = range.to(); + if (range.empty()) { + if (deleted && deleted > 0) // Handle deletion + from = Pos(from.line, from.ch - deleted); + else if (cm.state.overwrite && !cm.state.pasteIncoming) // Handle overwrite + to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); + } + var updateInput = cm.curOp.updateInput; + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, + origin: origin || (cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input")}; + makeChange(cm.doc, changeEvent); + signalLater(cm, "inputRead", cm, changeEvent); + } + if (inserted && !cm.state.pasteIncoming) + triggerElectric(cm, inserted); + + ensureCursorVisible(cm); + cm.curOp.updateInput = updateInput; + cm.curOp.typing = true; + cm.state.pasteIncoming = cm.state.cutIncoming = false; + } + + function triggerElectric(cm, inserted) { + // When an 'electric' character is inserted, immediately trigger a reindent + if (!cm.options.electricChars || !cm.options.smartIndent) return; + var sel = cm.doc.sel; + + for (var i = sel.ranges.length - 1; i >= 0; i--) { + var range = sel.ranges[i]; + if (range.head.ch > 100 || (i && sel.ranges[i - 1].head.line == range.head.line)) continue; + var mode = cm.getModeAt(range.head); + var indented = false; + if (mode.electricChars) { + for (var j = 0; j < mode.electricChars.length; j++) + if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indented = indentLine(cm, range.head.line, "smart"); + break; + } + } else if (mode.electricInput) { + if (mode.electricInput.test(getLine(cm.doc, range.head.line).text.slice(0, range.head.ch))) + indented = indentLine(cm, range.head.line, "smart"); + } + if (indented) signalLater(cm, "electricInput", cm, range.head.line); + } + } + + function copyableRanges(cm) { + var text = [], ranges = []; + for (var i = 0; i < cm.doc.sel.ranges.length; i++) { + var line = cm.doc.sel.ranges[i].head.line; + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; + ranges.push(lineRange); + text.push(cm.getRange(lineRange.anchor, lineRange.head)); + } + return {text: text, ranges: ranges}; + } + + function disableBrowserMagic(field) { + field.setAttribute("autocorrect", "off"); + field.setAttribute("autocapitalize", "off"); + field.setAttribute("spellcheck", "false"); + } + + // TEXTAREA INPUT STYLE + + function TextareaInput(cm) { + this.cm = cm; + // See input.poll and input.reset + this.prevInput = ""; + + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + this.pollingFast = false; + // Self-resetting timeout for the poller + this.polling = new Delayed(); + // Tracks when input.reset has punted to just putting a short + // string into the textarea instead of the full selection. + this.inaccurateSelection = false; + // Used to work around IE issue with selection being forgotten when focus moves away from textarea + this.hasSelection = false; + this.composing = null; + }; + + function hiddenTextarea() { + var te = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none"); + var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); + // The textarea is kept positioned near the cursor to prevent the + // fact that it'll be scrolled into view on input from scrolling + // our fake cursor out of view. On webkit, when wrap=off, paste is + // very slow. So make the area wide instead. + if (webkit) te.style.width = "1000px"; + else te.setAttribute("wrap", "off"); + // If border: 0; -- iOS fails to open keyboard (issue #1287) + if (ios) te.style.border = "1px solid black"; + disableBrowserMagic(te); + return div; + } + + TextareaInput.prototype = copyObj({ + init: function(display) { + var input = this, cm = this.cm; + + // Wraps and hides input textarea + var div = this.wrapper = hiddenTextarea(); + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + var te = this.textarea = div.firstChild; + display.wrapper.insertBefore(div, display.wrapper.firstChild); + + // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) + if (ios) te.style.width = "0px"; + + on(te, "input", function() { + if (ie && ie_version >= 9 && input.hasSelection) input.hasSelection = null; + input.poll(); + }); + + on(te, "paste", function() { + // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206 + // Add a char to the end of textarea before paste occur so that + // selection doesn't span to the end of textarea. + if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) { + var start = te.selectionStart, end = te.selectionEnd; + te.value += "$"; + // The selection end needs to be set before the start, otherwise there + // can be an intermediate non-empty selection between the two, which + // can override the middle-click paste buffer on linux and cause the + // wrong thing to get pasted. + te.selectionEnd = end; + te.selectionStart = start; + cm.state.fakedLastChar = true; + } + cm.state.pasteIncoming = true; + input.fastPoll(); + }); + + function prepareCopyCut(e) { + if (cm.somethingSelected()) { + lastCopied = cm.getSelections(); + if (input.inaccurateSelection) { + input.prevInput = ""; + input.inaccurateSelection = false; + te.value = lastCopied.join("\n"); + selectInput(te); + } + } else if (!cm.options.lineWiseCopyCut) { + return; + } else { + var ranges = copyableRanges(cm); + lastCopied = ranges.text; + if (e.type == "cut") { + cm.setSelections(ranges.ranges, null, sel_dontScroll); + } else { + input.prevInput = ""; + te.value = ranges.text.join("\n"); + selectInput(te); + } + } + if (e.type == "cut") cm.state.cutIncoming = true; + } + on(te, "cut", prepareCopyCut); + on(te, "copy", prepareCopyCut); + + on(display.scroller, "paste", function(e) { + if (eventInWidget(display, e)) return; + cm.state.pasteIncoming = true; + input.focus(); + }); + + // Prevent normal selection in the editor (we handle our own) + on(display.lineSpace, "selectstart", function(e) { + if (!eventInWidget(display, e)) e_preventDefault(e); + }); + + on(te, "compositionstart", function() { + var start = cm.getCursor("from"); + input.composing = { + start: start, + range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) + }; + }); + on(te, "compositionend", function() { + if (input.composing) { + input.poll(); + input.composing.range.clear(); + input.composing = null; + } + }); + }, + + prepareSelection: function() { + // Redraw the selection and/or cursor + var cm = this.cm, display = cm.display, doc = cm.doc; + var result = prepareSelection(cm); + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)); + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)); + } + + return result; + }, + + showSelection: function(drawn) { + var cm = this.cm, display = cm.display; + removeChildrenAndAdd(display.cursorDiv, drawn.cursors); + removeChildrenAndAdd(display.selectionDiv, drawn.selection); + if (drawn.teTop != null) { + this.wrapper.style.top = drawn.teTop + "px"; + this.wrapper.style.left = drawn.teLeft + "px"; + } + }, + + // Reset the input to correspond to the selection (or to be empty, + // when not typing and nothing is selected) + reset: function(typing) { + if (this.contextMenuPending) return; + var minimal, selected, cm = this.cm, doc = cm.doc; + if (cm.somethingSelected()) { + this.prevInput = ""; + var range = doc.sel.primary(); + minimal = hasCopyEvent && + (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000); + var content = minimal ? "-" : selected || cm.getSelection(); + this.textarea.value = content; + if (cm.state.focused) selectInput(this.textarea); + if (ie && ie_version >= 9) this.hasSelection = content; + } else if (!typing) { + this.prevInput = this.textarea.value = ""; + if (ie && ie_version >= 9) this.hasSelection = null; + } + this.inaccurateSelection = minimal; + }, + + getField: function() { return this.textarea; }, + + supportsTouch: function() { return false; }, + + focus: function() { + if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { + try { this.textarea.focus(); } + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM + } + }, + + blur: function() { this.textarea.blur(); }, + + resetPosition: function() { + this.wrapper.style.top = this.wrapper.style.left = 0; + }, + + receivedFocus: function() { this.slowPoll(); }, + + // Poll for input changes, using the normal rate of polling. This + // runs as long as the editor is focused. + slowPoll: function() { + var input = this; + if (input.pollingFast) return; + input.polling.set(this.cm.options.pollInterval, function() { + input.poll(); + if (input.cm.state.focused) input.slowPoll(); + }); + }, + + // When an event has just come in that is likely to add or change + // something in the input textarea, we poll faster, to ensure that + // the change appears on the screen quickly. + fastPoll: function() { + var missed = false, input = this; + input.pollingFast = true; + function p() { + var changed = input.poll(); + if (!changed && !missed) {missed = true; input.polling.set(60, p);} + else {input.pollingFast = false; input.slowPoll();} + } + input.polling.set(20, p); + }, + + // Read input from the textarea, and update the document to match. + // When something is selected, it is present in the textarea, and + // selected (unless it is huge, in which case a placeholder is + // used). When nothing is selected, the cursor sits after previously + // seen text (can be empty), which is stored in prevInput (we must + // not reset the textarea when typing, because that breaks IME). + poll: function() { + var cm = this.cm, input = this.textarea, prevInput = this.prevInput; + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (this.contextMenuPending || !cm.state.focused || + (hasSelection(input) && !prevInput) || + isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq) + return false; + + // See paste handler for more on the fakedLastChar kludge + if (cm.state.pasteIncoming && cm.state.fakedLastChar) { + input.value = input.value.substring(0, input.value.length - 1); + cm.state.fakedLastChar = false; + } + var text = input.value; + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) return false; + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && this.hasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { + cm.display.input.reset(); + return false; + } + + if (cm.doc.sel == cm.display.selForContextMenu) { + var first = text.charCodeAt(0); + if (first == 0x200b && !prevInput) prevInput = "\u200b"; + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo"); } + } + // Find the part of the input that is actually new + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; + + var self = this; + runInOp(cm, function() { + applyTextInput(cm, text.slice(same), prevInput.length - same, + null, self.composing ? "*compose" : null); + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = self.prevInput = ""; + else self.prevInput = text; + + if (self.composing) { + self.composing.range.clear(); + self.composing.range = cm.markText(self.composing.start, cm.getCursor("to"), + {className: "CodeMirror-composing"}); + } + }); + return true; + }, + + ensurePolled: function() { + if (this.pollingFast && this.poll()) this.pollingFast = false; + }, + + onKeyPress: function() { + if (ie && ie_version >= 9) this.hasSelection = null; + this.fastPoll(); + }, + + onContextMenu: function(e) { + var input = this, cm = input.cm, display = cm.display, te = input.textarea; + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; + if (!pos || presto) return; // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu; + if (reset && cm.doc.sel.contains(pos) == -1) + operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll); + + var oldCSS = te.style.cssText; + input.wrapper.style.position = "absolute"; + te.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " + + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + + "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712) + display.input.focus(); + if (webkit) window.scrollTo(null, oldScrollY); + display.input.reset(); + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) te.value = input.prevInput = " "; + input.contextMenuPending = true; + display.selForContextMenu = cm.doc.sel; + clearTimeout(display.detectingSelectAll); + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (te.selectionStart != null) { + var selected = cm.somethingSelected(); + var extval = "\u200b" + (selected ? te.value : ""); + te.value = "\u21da"; // Used to catch context-menu undo + te.value = extval; + input.prevInput = selected ? "" : "\u200b"; + te.selectionStart = 1; te.selectionEnd = extval.length; + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel; + } + } + function rehide() { + input.contextMenuPending = false; + input.wrapper.style.position = "relative"; + te.style.cssText = oldCSS; + if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); + + // Try to detect the user choosing select-all + if (te.selectionStart != null) { + if (!ie || (ie && ie_version < 9)) prepareSelectAllHack(); + var i = 0, poll = function() { + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && + te.selectionEnd > 0 && input.prevInput == "\u200b") + operation(cm, commands.selectAll)(cm); + else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500); + else display.input.reset(); + }; + display.detectingSelectAll = setTimeout(poll, 200); + } + } + + if (ie && ie_version >= 9) prepareSelectAllHack(); + if (captureRightClick) { + e_stop(e); + var mouseup = function() { + off(window, "mouseup", mouseup); + setTimeout(rehide, 20); + }; + on(window, "mouseup", mouseup); + } else { + setTimeout(rehide, 50); + } + }, + + setUneditable: nothing, + + needsContentAttribute: false + }, TextareaInput.prototype); + + // CONTENTEDITABLE INPUT STYLE + + function ContentEditableInput(cm) { + this.cm = cm; + this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null; + this.polling = new Delayed(); + this.gracePeriod = false; + } + + ContentEditableInput.prototype = copyObj({ + init: function(display) { + var input = this, cm = input.cm; + var div = input.div = display.lineDiv; + div.contentEditable = "true"; + disableBrowserMagic(div); + + on(div, "paste", function(e) { + var pasted = e.clipboardData && e.clipboardData.getData("text/plain"); + if (pasted) { + e.preventDefault(); + cm.replaceSelection(pasted, null, "paste"); + } + }); + + on(div, "compositionstart", function(e) { + var data = e.data; + input.composing = {sel: cm.doc.sel, data: data, startData: data}; + if (!data) return; + var prim = cm.doc.sel.primary(); + var line = cm.getLine(prim.head.line); + var found = line.indexOf(data, Math.max(0, prim.head.ch - data.length)); + if (found > -1 && found <= prim.head.ch) + input.composing.sel = simpleSelection(Pos(prim.head.line, found), + Pos(prim.head.line, found + data.length)); + }); + on(div, "compositionupdate", function(e) { + input.composing.data = e.data; + }); + on(div, "compositionend", function(e) { + var ours = input.composing; + if (!ours) return; + if (e.data != ours.startData && !/\u200b/.test(e.data)) + ours.data = e.data; + // Need a small delay to prevent other code (input event, + // selection polling) from doing damage when fired right after + // compositionend. + setTimeout(function() { + if (!ours.handled) + input.applyComposition(ours); + if (input.composing == ours) + input.composing = null; + }, 50); + }); + + on(div, "touchstart", function() { + input.forceCompositionEnd(); + }); + + on(div, "input", function() { + if (input.composing) return; + if (!input.pollContent()) + runInOp(input.cm, function() {regChange(cm);}); + }); + + function onCopyCut(e) { + if (cm.somethingSelected()) { + lastCopied = cm.getSelections(); + if (e.type == "cut") cm.replaceSelection("", null, "cut"); + } else if (!cm.options.lineWiseCopyCut) { + return; + } else { + var ranges = copyableRanges(cm); + lastCopied = ranges.text; + if (e.type == "cut") { + cm.operation(function() { + cm.setSelections(ranges.ranges, 0, sel_dontScroll); + cm.replaceSelection("", null, "cut"); + }); + } + } + // iOS exposes the clipboard API, but seems to discard content inserted into it + if (e.clipboardData && !ios) { + e.preventDefault(); + e.clipboardData.clearData(); + e.clipboardData.setData("text/plain", lastCopied.join("\n")); + } else { + // Old-fashioned briefly-focus-a-textarea hack + var kludge = hiddenTextarea(), te = kludge.firstChild; + cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); + te.value = lastCopied.join("\n"); + var hadFocus = document.activeElement; + selectInput(te); + setTimeout(function() { + cm.display.lineSpace.removeChild(kludge); + hadFocus.focus(); + }, 50); + } + } + on(div, "copy", onCopyCut); + on(div, "cut", onCopyCut); + }, + + prepareSelection: function() { + var result = prepareSelection(this.cm, false); + result.focus = this.cm.state.focused; + return result; + }, + + showSelection: function(info) { + if (!info || !this.cm.display.view.length) return; + if (info.focus) this.showPrimarySelection(); + this.showMultipleSelections(info); + }, + + showPrimarySelection: function() { + var sel = window.getSelection(), prim = this.cm.doc.sel.primary(); + var curAnchor = domToPos(this.cm, sel.anchorNode, sel.anchorOffset); + var curFocus = domToPos(this.cm, sel.focusNode, sel.focusOffset); + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && + cmp(minPos(curAnchor, curFocus), prim.from()) == 0 && + cmp(maxPos(curAnchor, curFocus), prim.to()) == 0) + return; + + var start = posToDOM(this.cm, prim.from()); + var end = posToDOM(this.cm, prim.to()); + if (!start && !end) return; + + var view = this.cm.display.view; + var old = sel.rangeCount && sel.getRangeAt(0); + if (!start) { + start = {node: view[0].measure.map[2], offset: 0}; + } else if (!end) { // FIXME dangerously hacky + var measure = view[view.length - 1].measure; + var map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map; + end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]}; + } + + try { var rng = range(start.node, start.offset, end.offset, end.node); } + catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible + if (rng) { + sel.removeAllRanges(); + sel.addRange(rng); + if (old && sel.anchorNode == null) sel.addRange(old); + else if (gecko) this.startGracePeriod(); + } + this.rememberSelection(); + }, + + startGracePeriod: function() { + var input = this; + clearTimeout(this.gracePeriod); + this.gracePeriod = setTimeout(function() { + input.gracePeriod = false; + if (input.selectionChanged()) + input.cm.operation(function() { input.cm.curOp.selectionChanged = true; }); + }, 20); + }, + + showMultipleSelections: function(info) { + removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors); + removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection); + }, + + rememberSelection: function() { + var sel = window.getSelection(); + this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset; + this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset; + }, + + selectionInEditor: function() { + var sel = window.getSelection(); + if (!sel.rangeCount) return false; + var node = sel.getRangeAt(0).commonAncestorContainer; + return contains(this.div, node); + }, + + focus: function() { + if (this.cm.options.readOnly != "nocursor") this.div.focus(); + }, + blur: function() { this.div.blur(); }, + getField: function() { return this.div; }, + + supportsTouch: function() { return true; }, + + receivedFocus: function() { + var input = this; + if (this.selectionInEditor()) + this.pollSelection(); + else + runInOp(this.cm, function() { input.cm.curOp.selectionChanged = true; }); + + function poll() { + if (input.cm.state.focused) { + input.pollSelection(); + input.polling.set(input.cm.options.pollInterval, poll); + } + } + this.polling.set(this.cm.options.pollInterval, poll); + }, + + selectionChanged: function() { + var sel = window.getSelection(); + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset; + }, + + pollSelection: function() { + if (!this.composing && !this.gracePeriod && this.selectionChanged()) { + var sel = window.getSelection(), cm = this.cm; + this.rememberSelection(); + var anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset); + var head = domToPos(cm, sel.focusNode, sel.focusOffset); + if (anchor && head) runInOp(cm, function() { + setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll); + if (anchor.bad || head.bad) cm.curOp.selectionChanged = true; + }); + } + }, + + pollContent: function() { + var cm = this.cm, display = cm.display, sel = cm.doc.sel.primary(); + var from = sel.from(), to = sel.to(); + if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false; + + var fromIndex; + if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { + var fromLine = lineNo(display.view[0].line); + var fromNode = display.view[0].node; + } else { + var fromLine = lineNo(display.view[fromIndex].line); + var fromNode = display.view[fromIndex - 1].node.nextSibling; + } + var toIndex = findViewIndex(cm, to.line); + if (toIndex == display.view.length - 1) { + var toLine = display.viewTo - 1; + var toNode = display.view[toIndex].node; + } else { + var toLine = lineNo(display.view[toIndex + 1].line) - 1; + var toNode = display.view[toIndex + 1].node.previousSibling; + } + + var newText = splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)); + var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)); + while (newText.length > 1 && oldText.length > 1) { + if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; } + else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++; } + else break; + } + + var cutFront = 0, cutEnd = 0; + var newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length); + while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) + ++cutFront; + var newBot = lst(newText), oldBot = lst(oldText); + var maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), + oldBot.length - (oldText.length == 1 ? cutFront : 0)); + while (cutEnd < maxCutEnd && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) + ++cutEnd; + + newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd); + newText[0] = newText[0].slice(cutFront); + + var chFrom = Pos(fromLine, cutFront); + var chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0); + if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { + replaceRange(cm.doc, newText, chFrom, chTo, "+input"); + return true; + } + }, + + ensurePolled: function() { + this.forceCompositionEnd(); + }, + reset: function() { + this.forceCompositionEnd(); + }, + forceCompositionEnd: function() { + if (!this.composing || this.composing.handled) return; + this.applyComposition(this.composing); + this.composing.handled = true; + this.div.blur(); + this.div.focus(); + }, + applyComposition: function(composing) { + if (composing.data && composing.data != composing.startData) + operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel); + }, + + setUneditable: function(node) { + node.setAttribute("contenteditable", "false"); + }, + + onKeyPress: function(e) { + e.preventDefault(); + operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0); + }, + + onContextMenu: nothing, + resetPosition: nothing, + + needsContentAttribute: true + }, ContentEditableInput.prototype); + + function posToDOM(cm, pos) { + var view = findViewForLine(cm, pos.line); + if (!view || view.hidden) return null; + var line = getLine(cm.doc, pos.line); + var info = mapFromLineView(view, line, pos.line); + + var order = getOrder(line), side = "left"; + if (order) { + var partPos = getBidiPartAt(order, pos.ch); + side = partPos % 2 ? "right" : "left"; + } + var result = nodeAndOffsetInLineMap(info.map, pos.ch, side); + result.offset = result.collapse == "right" ? result.end : result.start; + return result; + } + + function badPos(pos, bad) { if (bad) pos.bad = true; return pos; } + + function domToPos(cm, node, offset) { + var lineNode; + if (node == cm.display.lineDiv) { + lineNode = cm.display.lineDiv.childNodes[offset]; + if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true); + node = null; offset = 0; + } else { + for (lineNode = node;; lineNode = lineNode.parentNode) { + if (!lineNode || lineNode == cm.display.lineDiv) return null; + if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break; + } + } + for (var i = 0; i < cm.display.view.length; i++) { + var lineView = cm.display.view[i]; + if (lineView.node == lineNode) + return locateNodeInLineView(lineView, node, offset); + } + } + + function locateNodeInLineView(lineView, node, offset) { + var wrapper = lineView.text.firstChild, bad = false; + if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true); + if (node == wrapper) { + bad = true; + node = wrapper.childNodes[offset]; + offset = 0; + if (!node) { + var line = lineView.rest ? lst(lineView.rest) : lineView.line; + return badPos(Pos(lineNo(line), line.text.length), bad); + } + } + + var textNode = node.nodeType == 3 ? node : null, topNode = node; + if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { + textNode = node.firstChild; + if (offset) offset = textNode.nodeValue.length; + } + while (topNode.parentNode != wrapper) topNode = topNode.parentNode; + var measure = lineView.measure, maps = measure.maps; + + function find(textNode, topNode, offset) { + for (var i = -1; i < (maps ? maps.length : 0); i++) { + var map = i < 0 ? measure.map : maps[i]; + for (var j = 0; j < map.length; j += 3) { + var curNode = map[j + 2]; + if (curNode == textNode || curNode == topNode) { + var line = lineNo(i < 0 ? lineView.line : lineView.rest[i]); + var ch = map[j] + offset; + if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)]; + return Pos(line, ch); + } + } + } + } + var found = find(textNode, topNode, offset); + if (found) return badPos(found, bad); + + // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems + for (var after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { + found = find(after, after.firstChild, 0); + if (found) + return badPos(Pos(found.line, found.ch - dist), bad); + else + dist += after.textContent.length; + } + for (var before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) { + found = find(before, before.firstChild, -1); + if (found) + return badPos(Pos(found.line, found.ch + dist), bad); + else + dist += after.textContent.length; + } + } + + function domTextBetween(cm, from, to, fromLine, toLine) { + var text = "", closing = false; + function recognizeMarker(id) { return function(marker) { return marker.id == id; }; } + function walk(node) { + if (node.nodeType == 1) { + var cmText = node.getAttribute("cm-text"); + if (cmText != null) { + if (cmText == "") cmText = node.textContent.replace(/\u200b/g, ""); + text += cmText; + return; + } + var markerID = node.getAttribute("cm-marker"), range; + if (markerID) { + var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)); + if (found.length && (range = found[0].find())) + text += getBetween(cm.doc, range.from, range.to).join("\n"); + return; + } + if (node.getAttribute("contenteditable") == "false") return; + for (var i = 0; i < node.childNodes.length; i++) + walk(node.childNodes[i]); + if (/^(pre|div|p)$/i.test(node.nodeName)) + closing = true; + } else if (node.nodeType == 3) { + var val = node.nodeValue; + if (!val) return; + if (closing) { + text += "\n"; + closing = false; + } + text += val; + } + } + for (;;) { + walk(from); + if (from == to) break; + from = from.nextSibling; + } + return text; + } + + CodeMirror.inputStyles = {"textarea": TextareaInput, "contenteditable": ContentEditableInput}; + + // SELECTION / CURSOR + + // Selection objects are immutable. A new one is created every time + // the selection changes. A selection is one or more non-overlapping + // (and non-touching) ranges, sorted, and an integer that indicates + // which one is the primary selection (the one that's scrolled into + // view, that getCursor returns, etc). + function Selection(ranges, primIndex) { + this.ranges = ranges; + this.primIndex = primIndex; + } + + Selection.prototype = { + primary: function() { return this.ranges[this.primIndex]; }, + equals: function(other) { + if (other == this) return true; + if (other.primIndex != this.primIndex || other.ranges.length != this.ranges.length) return false; + for (var i = 0; i < this.ranges.length; i++) { + var here = this.ranges[i], there = other.ranges[i]; + if (cmp(here.anchor, there.anchor) != 0 || cmp(here.head, there.head) != 0) return false; + } + return true; + }, + deepCopy: function() { + for (var out = [], i = 0; i < this.ranges.length; i++) + out[i] = new Range(copyPos(this.ranges[i].anchor), copyPos(this.ranges[i].head)); + return new Selection(out, this.primIndex); + }, + somethingSelected: function() { + for (var i = 0; i < this.ranges.length; i++) + if (!this.ranges[i].empty()) return true; + return false; + }, + contains: function(pos, end) { + if (!end) end = pos; + for (var i = 0; i < this.ranges.length; i++) { + var range = this.ranges[i]; + if (cmp(end, range.from()) >= 0 && cmp(pos, range.to()) <= 0) + return i; + } + return -1; + } + }; + + function Range(anchor, head) { + this.anchor = anchor; this.head = head; + } + + Range.prototype = { + from: function() { return minPos(this.anchor, this.head); }, + to: function() { return maxPos(this.anchor, this.head); }, + empty: function() { + return this.head.line == this.anchor.line && this.head.ch == this.anchor.ch; + } + }; + + // Take an unsorted, potentially overlapping set of ranges, and + // build a selection out of it. 'Consumes' ranges array (modifying + // it). + function normalizeSelection(ranges, primIndex) { + var prim = ranges[primIndex]; + ranges.sort(function(a, b) { return cmp(a.from(), b.from()); }); + primIndex = indexOf(ranges, prim); + for (var i = 1; i < ranges.length; i++) { + var cur = ranges[i], prev = ranges[i - 1]; + if (cmp(prev.to(), cur.from()) >= 0) { + var from = minPos(prev.from(), cur.from()), to = maxPos(prev.to(), cur.to()); + var inv = prev.empty() ? cur.from() == cur.head : prev.from() == prev.head; + if (i <= primIndex) --primIndex; + ranges.splice(--i, 2, new Range(inv ? to : from, inv ? from : to)); + } + } + return new Selection(ranges, primIndex); + } + + function simpleSelection(anchor, head) { + return new Selection([new Range(anchor, head || anchor)], 0); + } + + // Most of the external API clips given positions to make sure they + // actually exist within the document. + function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));} + function clipPos(doc, pos) { + if (pos.line < doc.first) return Pos(doc.first, 0); + var last = doc.first + doc.size - 1; + if (pos.line > last) return Pos(last, getLine(doc, last).text.length); + return clipToLen(pos, getLine(doc, pos.line).text.length); + } + function clipToLen(pos, linelen) { + var ch = pos.ch; + if (ch == null || ch > linelen) return Pos(pos.line, linelen); + else if (ch < 0) return Pos(pos.line, 0); + else return pos; + } + function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} + function clipPosArray(doc, array) { + for (var out = [], i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]); + return out; + } + + // SELECTION UPDATES + + // The 'scroll' parameter given to many of these indicated whether + // the new cursor position should be scrolled into view after + // modifying the selection. + + // If shift is held or the extend flag is set, extends a range to + // include a given position (and optionally a second position). + // Otherwise, simply returns the range between the given positions. + // Used for cursor motion and such. + function extendRange(doc, range, head, other) { + if (doc.cm && doc.cm.display.shift || doc.extend) { + var anchor = range.anchor; + if (other) { + var posBefore = cmp(head, anchor) < 0; + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head; + head = other; + } else if (posBefore != (cmp(head, other) < 0)) { + head = other; + } + } + return new Range(anchor, head); + } else { + return new Range(other || head, head); + } + } + + // Extend the primary selection range, discard the rest. + function extendSelection(doc, head, other, options) { + setSelection(doc, new Selection([extendRange(doc, doc.sel.primary(), head, other)], 0), options); + } + + // Extend all selections (pos is an array of selections with length + // equal the number of selections) + function extendSelections(doc, heads, options) { + for (var out = [], i = 0; i < doc.sel.ranges.length; i++) + out[i] = extendRange(doc, doc.sel.ranges[i], heads[i], null); + var newSel = normalizeSelection(out, doc.sel.primIndex); + setSelection(doc, newSel, options); + } + + // Updates a single range in the selection. + function replaceOneSelection(doc, i, range, options) { + var ranges = doc.sel.ranges.slice(0); + ranges[i] = range; + setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options); + } + + // Reset the selection to a single range. + function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options); + } + + // Give beforeSelectionChange handlers a change to influence a + // selection update. + function filterSelectionChange(doc, sel) { + var obj = { + ranges: sel.ranges, + update: function(ranges) { + this.ranges = []; + for (var i = 0; i < ranges.length; i++) + this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)); + } + }; + signal(doc, "beforeSelectionChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); + if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1); + else return sel; + } + + function setSelectionReplaceHistory(doc, sel, options) { + var done = doc.history.done, last = lst(done); + if (last && last.ranges) { + done[done.length - 1] = sel; + setSelectionNoUndo(doc, sel, options); + } else { + setSelection(doc, sel, options); + } + } + + // Set a new selection. + function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options); + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options); + } + + function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + sel = filterSelectionChange(doc, sel); + + var bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); + + if (!(options && options.scroll === false) && doc.cm) + ensureCursorVisible(doc.cm); + } + + function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) return; + + doc.sel = sel; + + if (doc.cm) { + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; + signalCursorActivity(doc.cm); + } + signalLater(doc, "cursorActivity", doc); + } + + // Verify that the selection does not partially select any atomic + // marked ranges. + function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false), sel_dontScroll); + } + + // Return a selection that does not partially select any atomic + // ranges. + function skipAtomicInSelection(doc, sel, bias, mayClear) { + var out; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear); + var newHead = skipAtomic(doc, range.head, bias, mayClear); + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) out = sel.ranges.slice(0, i); + out[i] = new Range(newAnchor, newHead); + } + } + return out ? normalizeSelection(out, sel.primIndex) : sel; + } + + // Ensure a given position is not inside an atomic range. + function skipAtomic(doc, pos, bias, mayClear) { + var flipped = false, curPos = pos; + var dir = bias || 1; + doc.cantEdit = false; + search: for (;;) { + var line = getLine(doc, curPos.line); + if (line.markedSpans) { + for (var i = 0; i < line.markedSpans.length; ++i) { + var sp = line.markedSpans[i], m = sp.marker; + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter"); + if (m.explicitlyCleared) { + if (!line.markedSpans) break; + else {--i; continue;} + } + } + if (!m.atomic) continue; + var newPos = m.find(dir < 0 ? -1 : 1); + if (cmp(newPos, curPos) == 0) { + newPos.ch += dir; + if (newPos.ch < 0) { + if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1)); + else newPos = null; + } else if (newPos.ch > line.text.length) { + if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0); + else newPos = null; + } + if (!newPos) { + if (flipped) { + // Driven in a corner -- no valid cursor position found at all + // -- try again *with* clearing, if we didn't already + if (!mayClear) return skipAtomic(doc, pos, bias, true); + // Otherwise, turn off editing until further notice, and return the start of the doc + doc.cantEdit = true; + return Pos(doc.first, 0); + } + flipped = true; newPos = pos; dir = -dir; + } + } + curPos = newPos; + continue search; + } + } + } + return curPos; + } + } + + // SELECTION DRAWING + + function updateSelection(cm) { + cm.display.input.showSelection(cm.display.input.prepareSelection()); + } + + function prepareSelection(cm, primary) { + var doc = cm.doc, result = {}; + var curFragment = result.cursors = document.createDocumentFragment(); + var selFragment = result.selection = document.createDocumentFragment(); + + for (var i = 0; i < doc.sel.ranges.length; i++) { + if (primary === false && i == doc.sel.primIndex) continue; + var range = doc.sel.ranges[i]; + var collapsed = range.empty(); + if (collapsed || cm.options.showCursorWhenSelecting) + drawSelectionCursor(cm, range, curFragment); + if (!collapsed) + drawSelectionRange(cm, range, selFragment); + } + return result; + } + + // Draws a cursor for the given range + function drawSelectionCursor(cm, range, output) { + var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine); + + var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); + cursor.style.left = pos.left + "px"; + cursor.style.top = pos.top + "px"; + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")); + otherCursor.style.display = ""; + otherCursor.style.left = pos.other.left + "px"; + otherCursor.style.top = pos.other.top + "px"; + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; + } + } + + // Draws the given range as a highlighted selection + function drawSelectionRange(cm, range, output) { + var display = cm.display, doc = cm.doc; + var fragment = document.createDocumentFragment(); + var padding = paddingH(cm.display), leftSide = padding.left; + var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; + + function add(left, top, width, bottom) { + if (top < 0) top = 0; + top = Math.round(top); + bottom = Math.round(bottom); + fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + + "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) + + "px; height: " + (bottom - top) + "px")); + } + + function drawForLine(line, fromArg, toArg) { + var lineObj = getLine(doc, line); + var lineLen = lineObj.text.length; + var start, end; + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias); + } + + iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { + var leftPos = coords(from, "left"), rightPos, left, right; + if (from == to) { + rightPos = leftPos; + left = right = leftPos.left; + } else { + rightPos = coords(to - 1, "right"); + if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } + left = leftPos.left; + right = rightPos.right; + } + if (fromArg == null && from == 0) left = leftSide; + if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part + add(left, leftPos.top, null, leftPos.bottom); + left = leftSide; + if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); + } + if (toArg == null && to == lineLen) right = rightSide; + if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) + start = leftPos; + if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) + end = rightPos; + if (left < leftSide + 1) left = leftSide; + add(left, rightPos.top, right - left, rightPos.bottom); + }); + return {start: start, end: end}; + } + + var sFrom = range.from(), sTo = range.to(); + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch); + } else { + var fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line); + var singleVLine = visualLine(fromLine) == visualLine(toLine); + var leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end; + var rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start; + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom); + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); + } + } + if (leftEnd.bottom < rightStart.top) + add(leftSide, leftEnd.bottom, null, rightStart.top); + } + + output.appendChild(fragment); + } + + // Cursor-blinking + function restartBlink(cm) { + if (!cm.state.focused) return; + var display = cm.display; + clearInterval(display.blinker); + var on = true; + display.cursorDiv.style.visibility = ""; + if (cm.options.cursorBlinkRate > 0) + display.blinker = setInterval(function() { + display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; + }, cm.options.cursorBlinkRate); + else if (cm.options.cursorBlinkRate < 0) + display.cursorDiv.style.visibility = "hidden"; + } + + // HIGHLIGHT WORKER + + function startWorker(cm, time) { + if (cm.doc.mode.startState && cm.doc.frontier < cm.display.viewTo) + cm.state.highlight.set(time, bind(highlightWorker, cm)); + } + + function highlightWorker(cm) { + var doc = cm.doc; + if (doc.frontier < doc.first) doc.frontier = doc.first; + if (doc.frontier >= cm.display.viewTo) return; + var end = +new Date + cm.options.workTime; + var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); + var changedLines = []; + + doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) { + if (doc.frontier >= cm.display.viewFrom) { // Visible + var oldStyles = line.styles; + var highlighted = highlightLine(cm, line, state, true); + line.styles = highlighted.styles; + var oldCls = line.styleClasses, newCls = highlighted.classes; + if (newCls) line.styleClasses = newCls; + else if (oldCls) line.styleClasses = null; + var ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); + for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; + if (ischange) changedLines.push(doc.frontier); + line.stateAfter = copyState(doc.mode, state); + } else { + processLine(cm, line.text, state); + line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; + } + ++doc.frontier; + if (+new Date > end) { + startWorker(cm, cm.options.workDelay); + return true; + } + }); + if (changedLines.length) runInOp(cm, function() { + for (var i = 0; i < changedLines.length; i++) + regLineChange(cm, changedLines[i], "text"); + }); + } + + // Finds the line to start with when starting a parse. Tries to + // find a line with a stateAfter, so that it can start with a + // valid state. If that fails, it returns the line with the + // smallest indentation, which tends to need the least context to + // parse correctly. + function findStartLine(cm, n, precise) { + var minindent, minline, doc = cm.doc; + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); + for (var search = n; search > lim; --search) { + if (search <= doc.first) return doc.first; + var line = getLine(doc, search - 1); + if (line.stateAfter && (!precise || search <= doc.frontier)) return search; + var indented = countColumn(line.text, null, cm.options.tabSize); + if (minline == null || minindent > indented) { + minline = search - 1; + minindent = indented; + } + } + return minline; + } + + function getStateBefore(cm, n, precise) { + var doc = cm.doc, display = cm.display; + if (!doc.mode.startState) return true; + var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; + if (!state) state = startState(doc.mode); + else state = copyState(doc.mode, state); + doc.iter(pos, n, function(line) { + processLine(cm, line.text, state); + var save = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo; + line.stateAfter = save ? copyState(doc.mode, state) : null; + ++pos; + }); + if (precise) doc.frontier = pos; + return state; + } + + // POSITION MEASUREMENT + + function paddingTop(display) {return display.lineSpace.offsetTop;} + function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} + function paddingH(display) { + if (display.cachedPaddingH) return display.cachedPaddingH; + var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); + var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; + if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data; + return data; + } + + function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; } + function displayWidth(cm) { + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth; + } + function displayHeight(cm) { + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight; + } + + // Ensure the lineView.wrapping.heights array is populated. This is + // an array of bottom offsets for the lines that make up a drawn + // line. When lineWrapping is on, there might be more than one + // height. + function ensureLineHeights(cm, lineView, rect) { + var wrapping = cm.options.lineWrapping; + var curWidth = wrapping && displayWidth(cm); + if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { + var heights = lineView.measure.heights = []; + if (wrapping) { + lineView.measure.width = curWidth; + var rects = lineView.text.firstChild.getClientRects(); + for (var i = 0; i < rects.length - 1; i++) { + var cur = rects[i], next = rects[i + 1]; + if (Math.abs(cur.bottom - next.bottom) > 2) + heights.push((cur.bottom + next.top) / 2 - rect.top); + } + } + heights.push(rect.bottom - rect.top); + } + } + + // Find a line map (mapping character offsets to text nodes) and a + // measurement cache for the given line number. (A line view might + // contain multiple lines when collapsed ranges are present.) + function mapFromLineView(lineView, line, lineN) { + if (lineView.line == line) + return {map: lineView.measure.map, cache: lineView.measure.cache}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineView.rest[i] == line) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]}; + for (var i = 0; i < lineView.rest.length; i++) + if (lineNo(lineView.rest[i]) > lineN) + return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i], before: true}; + } + + // Render a line into the hidden node display.externalMeasured. Used + // when measurement is needed for a line that's not in the viewport. + function updateExternalMeasurement(cm, line) { + line = visualLine(line); + var lineN = lineNo(line); + var view = cm.display.externalMeasured = new LineView(cm.doc, line, lineN); + view.lineN = lineN; + var built = view.built = buildLineContent(cm, view); + view.text = built.pre; + removeChildrenAndAdd(cm.display.lineMeasure, built.pre); + return view; + } + + // Get a {top, bottom, left, right} box (in line-local coordinates) + // for a given character. + function measureChar(cm, line, ch, bias) { + return measureCharPrepared(cm, prepareMeasureForLine(cm, line), ch, bias); + } + + // Find a line view that corresponds to the given line number. + function findViewForLine(cm, lineN) { + if (lineN >= cm.display.viewFrom && lineN < cm.display.viewTo) + return cm.display.view[findViewIndex(cm, lineN)]; + var ext = cm.display.externalMeasured; + if (ext && lineN >= ext.lineN && lineN < ext.lineN + ext.size) + return ext; + } + + // Measurement can be split in two steps, the set-up work that + // applies to the whole line, and the measurement of the actual + // character. Functions like coordsChar, that need to do a lot of + // measurements in a row, can thus ensure that the set-up work is + // only done once. + function prepareMeasureForLine(cm, line) { + var lineN = lineNo(line); + var view = findViewForLine(cm, lineN); + if (view && !view.text) + view = null; + else if (view && view.changes) + updateLineForChanges(cm, view, lineN, getDimensions(cm)); + if (!view) + view = updateExternalMeasurement(cm, line); + + var info = mapFromLineView(view, line, lineN); + return { + line: line, view: view, rect: null, + map: info.map, cache: info.cache, before: info.before, + hasHeights: false + }; + } + + // Given a prepared measurement object, measures the position of an + // actual character (or fetches it from the cache). + function measureCharPrepared(cm, prepared, ch, bias, varHeight) { + if (prepared.before) ch = -1; + var key = ch + (bias || ""), found; + if (prepared.cache.hasOwnProperty(key)) { + found = prepared.cache[key]; + } else { + if (!prepared.rect) + prepared.rect = prepared.view.text.getBoundingClientRect(); + if (!prepared.hasHeights) { + ensureLineHeights(cm, prepared.view, prepared.rect); + prepared.hasHeights = true; + } + found = measureCharInner(cm, prepared, ch, bias); + if (!found.bogus) prepared.cache[key] = found; + } + return {left: found.left, right: found.right, + top: varHeight ? found.rtop : found.top, + bottom: varHeight ? found.rbottom : found.bottom}; + } + + var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; + + function nodeAndOffsetInLineMap(map, ch, bias) { + var node, start, end, collapse; + // First, search the line map for the text node corresponding to, + // or closest to, the target character. + for (var i = 0; i < map.length; i += 3) { + var mStart = map[i], mEnd = map[i + 1]; + if (ch < mStart) { + start = 0; end = 1; + collapse = "left"; + } else if (ch < mEnd) { + start = ch - mStart; + end = start + 1; + } else if (i == map.length - 3 || ch == mEnd && map[i + 3] > ch) { + end = mEnd - mStart; + start = end - 1; + if (ch >= mEnd) collapse = "right"; + } + if (start != null) { + node = map[i + 2]; + if (mStart == mEnd && bias == (node.insertLeft ? "left" : "right")) + collapse = bias; + if (bias == "left" && start == 0) + while (i && map[i - 2] == map[i - 3] && map[i - 1].insertLeft) { + node = map[(i -= 3) + 2]; + collapse = "left"; + } + if (bias == "right" && start == mEnd - mStart) + while (i < map.length - 3 && map[i + 3] == map[i + 4] && !map[i + 5].insertLeft) { + node = map[(i += 3) + 2]; + collapse = "right"; + } + break; + } + } + return {node: node, start: start, end: end, collapse: collapse, coverStart: mStart, coverEnd: mEnd}; + } + + function measureCharInner(cm, prepared, ch, bias) { + var place = nodeAndOffsetInLineMap(prepared.map, ch, bias); + var node = place.node, start = place.start, end = place.end, collapse = place.collapse; + + var rect; + if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. + for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned + while (start && isExtendingChar(prepared.line.text.charAt(place.coverStart + start))) --start; + while (place.coverStart + end < place.coverEnd && isExtendingChar(prepared.line.text.charAt(place.coverStart + end))) ++end; + if (ie && ie_version < 9 && start == 0 && end == place.coverEnd - place.coverStart) { + rect = node.parentNode.getBoundingClientRect(); + } else if (ie && cm.options.lineWrapping) { + var rects = range(node, start, end).getClientRects(); + if (rects.length) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = nullRect; + } else { + rect = range(node, start, end).getBoundingClientRect() || nullRect; + } + if (rect.left || rect.right || start == 0) break; + end = start; + start = start - 1; + collapse = "right"; + } + if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect); + } else { // If it is a widget, simply get the box for the whole widget. + if (start > 0) collapse = bias = "right"; + var rects; + if (cm.options.lineWrapping && (rects = node.getClientRects()).length > 1) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = node.getBoundingClientRect(); + } + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { + var rSpan = node.parentNode.getClientRects()[0]; + if (rSpan) + rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; + else + rect = nullRect; + } + + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; + var mid = (rtop + rbot) / 2; + var heights = prepared.view.measure.heights; + for (var i = 0; i < heights.length - 1; i++) + if (mid < heights[i]) break; + var top = i ? heights[i - 1] : 0, bot = heights[i]; + var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, + right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, + top: top, bottom: bot}; + if (!rect.left && !rect.right) result.bogus = true; + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } + + return result; + } + + // Work around problem with bounding client rects on ranges being + // returned incorrectly when zoomed on IE10 and below. + function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + return rect; + var scaleX = screen.logicalXDPI / screen.deviceXDPI; + var scaleY = screen.logicalYDPI / screen.deviceYDPI; + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY}; + } + + function clearLineMeasurementCacheFor(lineView) { + if (lineView.measure) { + lineView.measure.cache = {}; + lineView.measure.heights = null; + if (lineView.rest) for (var i = 0; i < lineView.rest.length; i++) + lineView.measure.caches[i] = {}; + } + } + + function clearLineMeasurementCache(cm) { + cm.display.externalMeasure = null; + removeChildren(cm.display.lineMeasure); + for (var i = 0; i < cm.display.view.length; i++) + clearLineMeasurementCacheFor(cm.display.view[i]); + } + + function clearCaches(cm) { + clearLineMeasurementCache(cm); + cm.display.cachedCharWidth = cm.display.cachedTextHeight = cm.display.cachedPaddingH = null; + if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; + cm.display.lineNumChars = null; + } + + function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } + function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } + + // Converts a {top, bottom, left, right} box from line-local + // coordinates into another coordinate system. Context may be one of + // "line", "div" (display.lineDiv), "local"/null (editor), "window", + // or "page". + function intoCoordSystem(cm, lineObj, rect, context) { + if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { + var size = widgetHeight(lineObj.widgets[i]); + rect.top += size; rect.bottom += size; + } + if (context == "line") return rect; + if (!context) context = "local"; + var yOff = heightAtLine(lineObj); + if (context == "local") yOff += paddingTop(cm.display); + else yOff -= cm.display.viewOffset; + if (context == "page" || context == "window") { + var lOff = cm.display.lineSpace.getBoundingClientRect(); + yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); + var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); + rect.left += xOff; rect.right += xOff; + } + rect.top += yOff; rect.bottom += yOff; + return rect; + } + + // Coverts a box from "div" coords to another coordinate system. + // Context may be "window", "page", "div", or "local"/null. + function fromCoordSystem(cm, coords, context) { + if (context == "div") return coords; + var left = coords.left, top = coords.top; + // First move into "page" coordinate system + if (context == "page") { + left -= pageScrollX(); + top -= pageScrollY(); + } else if (context == "local" || !context) { + var localBox = cm.display.sizer.getBoundingClientRect(); + left += localBox.left; + top += localBox.top; + } + + var lineSpaceBox = cm.display.lineSpace.getBoundingClientRect(); + return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; + } + + function charCoords(cm, pos, context, lineObj, bias) { + if (!lineObj) lineObj = getLine(cm.doc, pos.line); + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, bias), context); + } + + // Returns a box for a given cursor position, which may have an + // 'other' property containing the position of the secondary cursor + // on a bidi boundary. + function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { + lineObj = lineObj || getLine(cm.doc, pos.line); + if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj); + function get(ch, right) { + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); + if (right) m.left = m.right; else m.right = m.left; + return intoCoordSystem(cm, lineObj, m, context); + } + function getBidi(ch, partPos) { + var part = order[partPos], right = part.level % 2; + if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { + part = order[--partPos]; + ch = bidiRight(part) - (part.level % 2 ? 0 : 1); + right = true; + } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { + part = order[++partPos]; + ch = bidiLeft(part) - part.level % 2; + right = false; + } + if (right && ch == part.to && ch > part.from) return get(ch - 1); + return get(ch, right); + } + var order = getOrder(lineObj), ch = pos.ch; + if (!order) return get(ch); + var partPos = getBidiPartAt(order, ch); + var val = getBidi(ch, partPos); + if (bidiOther != null) val.other = getBidi(ch, bidiOther); + return val; + } + + // Used to cheaply estimate the coordinates for a position. Used for + // intermediate scroll updates. + function estimateCoords(cm, pos) { + var left = 0, pos = clipPos(cm.doc, pos); + if (!cm.options.lineWrapping) left = charWidth(cm.display) * pos.ch; + var lineObj = getLine(cm.doc, pos.line); + var top = heightAtLine(lineObj) + paddingTop(cm.display); + return {left: left, right: left, top: top, bottom: top + lineObj.height}; + } + + // Positions returned by coordsChar contain some extra information. + // xRel is the relative x position of the input coordinates compared + // to the found position (so xRel > 0 means the coordinates are to + // the right of the character position, for example). When outside + // is true, that means the coordinates lie outside the line's + // vertical range. + function PosWithInfo(line, ch, outside, xRel) { + var pos = Pos(line, ch); + pos.xRel = xRel; + if (outside) pos.outside = true; + return pos; + } + + // Compute the character position closest to the given coordinates. + // Input must be lineSpace-local ("div" coordinate system). + function coordsChar(cm, x, y) { + var doc = cm.doc; + y += cm.display.viewOffset; + if (y < 0) return PosWithInfo(doc.first, 0, true, -1); + var lineN = lineAtHeight(doc, y), last = doc.first + doc.size - 1; + if (lineN > last) + return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); + if (x < 0) x = 0; + + var lineObj = getLine(doc, lineN); + for (;;) { + var found = coordsCharInner(cm, lineObj, lineN, x, y); + var merged = collapsedSpanAtEnd(lineObj); + var mergedPos = merged && merged.find(0, true); + if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) + lineN = lineNo(lineObj = mergedPos.to.line); + else + return found; + } + } + + function coordsCharInner(cm, lineObj, lineNo, x, y) { + var innerOff = y - heightAtLine(lineObj); + var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; + var preparedMeasure = prepareMeasureForLine(cm, lineObj); + + function getX(ch) { + var sp = cursorCoords(cm, Pos(lineNo, ch), "line", lineObj, preparedMeasure); + wrongLine = true; + if (innerOff > sp.bottom) return sp.left - adjust; + else if (innerOff < sp.top) return sp.left + adjust; + else wrongLine = false; + return sp.left; + } + + var bidi = getOrder(lineObj), dist = lineObj.text.length; + var from = lineLeft(lineObj), to = lineRight(lineObj); + var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; + + if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); + // Do a binary search between these bounds. + for (;;) { + if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { + var ch = x < fromX || x - fromX <= toX - x ? from : to; + var xDiff = x - (ch == from ? fromX : toX); + while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; + var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, + xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0); + return pos; + } + var step = Math.ceil(dist / 2), middle = from + step; + if (bidi) { + middle = from; + for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); + } + var middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} + else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} + } + } + + var measureText; + // Compute the default text height. + function textHeight(display) { + if (display.cachedTextHeight != null) return display.cachedTextHeight; + if (measureText == null) { + measureText = elt("pre"); + // Measure a bunch of lines, for browsers that compute + // fractional heights. + for (var i = 0; i < 49; ++i) { + measureText.appendChild(document.createTextNode("x")); + measureText.appendChild(elt("br")); + } + measureText.appendChild(document.createTextNode("x")); + } + removeChildrenAndAdd(display.measure, measureText); + var height = measureText.offsetHeight / 50; + if (height > 3) display.cachedTextHeight = height; + removeChildren(display.measure); + return height || 1; + } + + // Compute the default character width. + function charWidth(display) { + if (display.cachedCharWidth != null) return display.cachedCharWidth; + var anchor = elt("span", "xxxxxxxxxx"); + var pre = elt("pre", [anchor]); + removeChildrenAndAdd(display.measure, pre); + var rect = anchor.getBoundingClientRect(), width = (rect.right - rect.left) / 10; + if (width > 2) display.cachedCharWidth = width; + return width || 10; + } + + // OPERATIONS + + // Operations are used to wrap a series of changes to the editor + // state in such a way that each change won't have to update the + // cursor and display (which would be awkward, slow, and + // error-prone). Instead, display updates are batched and then all + // combined and executed at once. + + var operationGroup = null; + + var nextOpId = 0; + // Start a new operation. + function startOperation(cm) { + cm.curOp = { + cm: cm, + viewChanged: false, // Flag that indicates that lines might need to be redrawn + startHeight: cm.doc.height, // Used to detect need to update scrollbar + forceUpdate: false, // Used to force a redraw + updateInput: null, // Whether to reset the input textarea + typing: false, // Whether this reset should be careful to leave existing text (for compositing) + changeObjs: null, // Accumulated changes, for firing change events + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already + selectionChanged: false, // Whether the selection needs to be redrawn + updateMaxLine: false, // Set when the widest line needs to be determined anew + scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet + scrollToPos: null, // Used to scroll to a specific position + focus: false, + id: ++nextOpId // Unique ID + }; + if (operationGroup) { + operationGroup.ops.push(cm.curOp); + } else { + cm.curOp.ownsGroup = operationGroup = { + ops: [cm.curOp], + delayedCallbacks: [] + }; + } + } + + function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + var callbacks = group.delayedCallbacks, i = 0; + do { + for (; i < callbacks.length; i++) + callbacks[i](); + for (var j = 0; j < group.ops.length; j++) { + var op = group.ops[j]; + if (op.cursorActivityHandlers) + while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + op.cursorActivityHandlers[op.cursorActivityCalled++](op.cm); + } + } while (i < callbacks.length); + } + + // Finish an operation, updating the display and signalling delayed events + function endOperation(cm) { + var op = cm.curOp, group = op.ownsGroup; + if (!group) return; + + try { fireCallbacksForOps(group); } + finally { + operationGroup = null; + for (var i = 0; i < group.ops.length; i++) + group.ops[i].cm.curOp = null; + endOperations(group); + } + } + + // The DOM updates done when an operation finishes are batched so + // that the minimum number of relayouts are required. + function endOperations(group) { + var ops = group.ops; + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R1(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W1(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R2(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W2(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_finish(ops[i]); + } + + function endOperation_R1(op) { + var cm = op.cm, display = cm.display; + maybeClipScrollbars(cm); + if (op.updateMaxLine) findMaxLine(cm); + + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping; + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); + } + + function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); + } + + function endOperation_R2(op) { + var cm = op.cm, display = cm.display; + if (op.updatedDisplay) updateHeightsInViewport(cm); + + op.barMeasure = measureForScrollbars(cm); + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + // updateDisplay_W2 will use these properties to do the actual resizing + if (display.maxLineChanged && !cm.options.lineWrapping) { + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; + cm.display.sizerWidth = op.adjustWidthTo; + op.barMeasure.scrollWidth = + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); + } + + if (op.updatedDisplay || op.selectionChanged) + op.preparedSelection = display.input.prepareSelection(); + } + + function endOperation_W2(op) { + var cm = op.cm; + + if (op.adjustWidthTo != null) { + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; + if (op.maxScrollLeft < cm.doc.scrollLeft) + setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); + cm.display.maxLineChanged = false; + } + + if (op.preparedSelection) + cm.display.input.showSelection(op.preparedSelection); + if (op.updatedDisplay) + setDocumentHeight(cm, op.barMeasure); + if (op.updatedDisplay || op.startHeight != cm.doc.height) + updateScrollbars(cm, op.barMeasure); + + if (op.selectionChanged) restartBlink(cm); + + if (cm.state.focused && op.updateInput) + cm.display.input.reset(op.typing); + if (op.focus && op.focus == activeElt()) ensureFocus(op.cm); + } + + function endOperation_finish(op) { + var cm = op.cm, display = cm.display, doc = cm.doc; + + if (op.updatedDisplay) postUpdateDisplay(cm, op.update); + + // Abort mouse wheel delta measurement, when scrolling explicitly + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) + display.wheelStartX = display.wheelStartY = null; + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) { + doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)); + display.scrollbars.setScrollTop(doc.scrollTop); + display.scroller.scrollTop = doc.scrollTop; + } + if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { + doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - displayWidth(cm), op.scrollLeft)); + display.scrollbars.setScrollLeft(doc.scrollLeft); + display.scroller.scrollLeft = doc.scrollLeft; + alignHorizontally(cm); + } + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); + if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); + } + + // Fire events for markers that are hidden/unidden by editing or + // undoing + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; + if (hidden) for (var i = 0; i < hidden.length; ++i) + if (!hidden[i].lines.length) signal(hidden[i], "hide"); + if (unhidden) for (var i = 0; i < unhidden.length; ++i) + if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); + + if (display.wrapper.offsetHeight) + doc.scrollTop = cm.display.scroller.scrollTop; + + // Fire change events, and delayed event handlers + if (op.changeObjs) + signal(cm, "changes", cm, op.changeObjs); + if (op.update) + op.update.finish(); + } + + // Run the given function in an operation + function runInOp(cm, f) { + if (cm.curOp) return f(); + startOperation(cm); + try { return f(); } + finally { endOperation(cm); } + } + // Wraps a function in an operation. Returns the wrapped function. + function operation(cm, f) { + return function() { + if (cm.curOp) return f.apply(cm, arguments); + startOperation(cm); + try { return f.apply(cm, arguments); } + finally { endOperation(cm); } + }; + } + // Used to add methods to editor and doc instances, wrapping them in + // operations. + function methodOp(f) { + return function() { + if (this.curOp) return f.apply(this, arguments); + startOperation(this); + try { return f.apply(this, arguments); } + finally { endOperation(this); } + }; + } + function docMethodOp(f) { + return function() { + var cm = this.cm; + if (!cm || cm.curOp) return f.apply(this, arguments); + startOperation(cm); + try { return f.apply(this, arguments); } + finally { endOperation(cm); } + }; + } + + // VIEW TRACKING + + // These objects are used to represent the visible (currently drawn) + // part of the document. A LineView may correspond to multiple + // logical lines, if those are connected by collapsed ranges. + function LineView(doc, line, lineN) { + // The starting line + this.line = line; + // Continuing lines, if any + this.rest = visualLineContinued(line); + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1; + this.node = this.text = null; + this.hidden = lineIsHidden(doc, line); + } + + // Create a range of LineView objects for the given lines. + function buildViewArray(cm, from, to) { + var array = [], nextPos; + for (var pos = from; pos < to; pos = nextPos) { + var view = new LineView(cm.doc, getLine(cm.doc, pos), pos); + nextPos = pos + view.size; + array.push(view); + } + return array; + } + + // Updates the display.view data structure for a given change to the + // document. From and to are in pre-change coordinates. Lendiff is + // the amount of lines added or subtracted by the change. This is + // used for changes that span multiple lines, or change the way + // lines are divided into visual lines. regLineChange (below) + // registers single-line changes. + function regChange(cm, from, to, lendiff) { + if (from == null) from = cm.doc.first; + if (to == null) to = cm.doc.first + cm.doc.size; + if (!lendiff) lendiff = 0; + + var display = cm.display; + if (lendiff && to < display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers > from)) + display.updateLineNumbers = from; + + cm.curOp.viewChanged = true; + + if (from >= display.viewTo) { // Change after + if (sawCollapsedSpans && visualLineNo(cm.doc, from) < display.viewTo) + resetView(cm); + } else if (to <= display.viewFrom) { // Change before + if (sawCollapsedSpans && visualLineEndNo(cm.doc, to + lendiff) > display.viewFrom) { + resetView(cm); + } else { + display.viewFrom += lendiff; + display.viewTo += lendiff; + } + } else if (from <= display.viewFrom && to >= display.viewTo) { // Full overlap + resetView(cm); + } else if (from <= display.viewFrom) { // Top overlap + var cut = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cut) { + display.view = display.view.slice(cut.index); + display.viewFrom = cut.lineN; + display.viewTo += lendiff; + } else { + resetView(cm); + } + } else if (to >= display.viewTo) { // Bottom overlap + var cut = viewCuttingPoint(cm, from, from, -1); + if (cut) { + display.view = display.view.slice(0, cut.index); + display.viewTo = cut.lineN; + } else { + resetView(cm); + } + } else { // Gap in the middle + var cutTop = viewCuttingPoint(cm, from, from, -1); + var cutBot = viewCuttingPoint(cm, to, to + lendiff, 1); + if (cutTop && cutBot) { + display.view = display.view.slice(0, cutTop.index) + .concat(buildViewArray(cm, cutTop.lineN, cutBot.lineN)) + .concat(display.view.slice(cutBot.index)); + display.viewTo += lendiff; + } else { + resetView(cm); + } + } + + var ext = display.externalMeasured; + if (ext) { + if (to < ext.lineN) + ext.lineN += lendiff; + else if (from < ext.lineN + ext.size) + display.externalMeasured = null; + } + } + + // Register a change to a single line. Type must be one of "text", + // "gutter", "class", "widget" + function regLineChange(cm, line, type) { + cm.curOp.viewChanged = true; + var display = cm.display, ext = cm.display.externalMeasured; + if (ext && line >= ext.lineN && line < ext.lineN + ext.size) + display.externalMeasured = null; + + if (line < display.viewFrom || line >= display.viewTo) return; + var lineView = display.view[findViewIndex(cm, line)]; + if (lineView.node == null) return; + var arr = lineView.changes || (lineView.changes = []); + if (indexOf(arr, type) == -1) arr.push(type); + } + + // Clear the view. + function resetView(cm) { + cm.display.viewFrom = cm.display.viewTo = cm.doc.first; + cm.display.view = []; + cm.display.viewOffset = 0; + } + + // Find the view element corresponding to a given line. Return null + // when the line isn't visible. + function findViewIndex(cm, n) { + if (n >= cm.display.viewTo) return null; + n -= cm.display.viewFrom; + if (n < 0) return null; + var view = cm.display.view; + for (var i = 0; i < view.length; i++) { + n -= view[i].size; + if (n < 0) return i; + } + } + + function viewCuttingPoint(cm, oldN, newN, dir) { + var index = findViewIndex(cm, oldN), diff, view = cm.display.view; + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) + return {index: index, lineN: newN}; + for (var i = 0, n = cm.display.viewFrom; i < index; i++) + n += view[i].size; + if (n != oldN) { + if (dir > 0) { + if (index == view.length - 1) return null; + diff = (n + view[index].size) - oldN; + index++; + } else { + diff = n - oldN; + } + oldN += diff; newN += diff; + } + while (visualLineNo(cm.doc, newN) != newN) { + if (index == (dir < 0 ? 0 : view.length - 1)) return null; + newN += dir * view[index - (dir < 0 ? 1 : 0)].size; + index += dir; + } + return {index: index, lineN: newN}; + } + + // Force the view to cover a given range, adding empty view element + // or clipping off existing ones as needed. + function adjustView(cm, from, to) { + var display = cm.display, view = display.view; + if (view.length == 0 || from >= display.viewTo || to <= display.viewFrom) { + display.view = buildViewArray(cm, from, to); + display.viewFrom = from; + } else { + if (display.viewFrom > from) + display.view = buildViewArray(cm, from, display.viewFrom).concat(display.view); + else if (display.viewFrom < from) + display.view = display.view.slice(findViewIndex(cm, from)); + display.viewFrom = from; + if (display.viewTo < to) + display.view = display.view.concat(buildViewArray(cm, display.viewTo, to)); + else if (display.viewTo > to) + display.view = display.view.slice(0, findViewIndex(cm, to)); + } + display.viewTo = to; + } + + // Count the number of lines in the view whose DOM representation is + // out of date (or nonexistent). + function countDirtyView(cm) { + var view = cm.display.view, dirty = 0; + for (var i = 0; i < view.length; i++) { + var lineView = view[i]; + if (!lineView.hidden && (!lineView.node || lineView.changes)) ++dirty; + } + return dirty; + } + + // EVENT HANDLERS + + // Attach the necessary event handlers when initializing the editor + function registerEventHandlers(cm) { + var d = cm.display; + on(d.scroller, "mousedown", operation(cm, onMouseDown)); + // Older IE's will not fire a second mousedown for a double click + if (ie && ie_version < 11) + on(d.scroller, "dblclick", operation(cm, function(e) { + if (signalDOMEvent(cm, e)) return; + var pos = posFromMouse(cm, e); + if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; + e_preventDefault(e); + var word = cm.findWordAt(pos); + extendSelection(cm.doc, word.anchor, word.head); + })); + else + on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); + // Some browsers fire contextmenu *after* opening the menu, at + // which point we can't mess with it anymore. Context menu is + // handled in onMouseDown for these browsers. + if (!captureRightClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); + + // Used to suppress mouse event handling when a touch happens + var touchFinished, prevTouch = {end: 0}; + function finishTouch() { + if (d.activeTouch) { + touchFinished = setTimeout(function() {d.activeTouch = null;}, 1000); + prevTouch = d.activeTouch; + prevTouch.end = +new Date; + } + }; + function isMouseLikeTouchEvent(e) { + if (e.touches.length != 1) return false; + var touch = e.touches[0]; + return touch.radiusX <= 1 && touch.radiusY <= 1; + } + function farAway(touch, other) { + if (other.left == null) return true; + var dx = other.left - touch.left, dy = other.top - touch.top; + return dx * dx + dy * dy > 20 * 20; + } + on(d.scroller, "touchstart", function(e) { + if (!isMouseLikeTouchEvent(e)) { + clearTimeout(touchFinished); + var now = +new Date; + d.activeTouch = {start: now, moved: false, + prev: now - prevTouch.end <= 300 ? prevTouch : null}; + if (e.touches.length == 1) { + d.activeTouch.left = e.touches[0].pageX; + d.activeTouch.top = e.touches[0].pageY; + } + } + }); + on(d.scroller, "touchmove", function() { + if (d.activeTouch) d.activeTouch.moved = true; + }); + on(d.scroller, "touchend", function(e) { + var touch = d.activeTouch; + if (touch && !eventInWidget(d, e) && touch.left != null && + !touch.moved && new Date - touch.start < 300) { + var pos = cm.coordsChar(d.activeTouch, "page"), range; + if (!touch.prev || farAway(touch, touch.prev)) // Single tap + range = new Range(pos, pos); + else if (!touch.prev.prev || farAway(touch, touch.prev.prev)) // Double tap + range = cm.findWordAt(pos); + else // Triple tap + range = new Range(Pos(pos.line, 0), clipPos(cm.doc, Pos(pos.line + 1, 0))); + cm.setSelection(range.anchor, range.head); + cm.focus(); + e_preventDefault(e); + } + finishTouch(); + }); + on(d.scroller, "touchcancel", finishTouch); + + // Sync scrolling between fake scrollbars and real scrollable + // area, ensure viewport is updated when scrolling. + on(d.scroller, "scroll", function() { + if (d.scroller.clientHeight) { + setScrollTop(cm, d.scroller.scrollTop); + setScrollLeft(cm, d.scroller.scrollLeft, true); + signal(cm, "scroll", cm); + } + }); + + // Listen to wheel events in order to try and update the viewport on time. + on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); + on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); + + // Prevent wrapper from ever scrolling + on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); + + d.dragFunctions = { + simple: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);}, + start: function(e){onDragStart(cm, e);}, + drop: operation(cm, onDrop) + }; + + var inp = d.input.getField(); + on(inp, "keyup", function(e) { onKeyUp.call(cm, e); }); + on(inp, "keydown", operation(cm, onKeyDown)); + on(inp, "keypress", operation(cm, onKeyPress)); + on(inp, "focus", bind(onFocus, cm)); + on(inp, "blur", bind(onBlur, cm)); + } + + function dragDropChanged(cm, value, old) { + var wasOn = old && old != CodeMirror.Init; + if (!value != !wasOn) { + var funcs = cm.display.dragFunctions; + var toggle = value ? on : off; + toggle(cm.display.scroller, "dragstart", funcs.start); + toggle(cm.display.scroller, "dragenter", funcs.simple); + toggle(cm.display.scroller, "dragover", funcs.simple); + toggle(cm.display.scroller, "drop", funcs.drop); + } + } + + // Called when the window resizes + function onResize(cm) { + var d = cm.display; + if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) + return; + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + d.scrollbarsClipped = false; + cm.setSize(); + } + + // MOUSE EVENTS + + // Return true when the given mouse event happened in a widget + function eventInWidget(display, e) { + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { + if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || + (n.parentNode == display.sizer && n != display.mover)) + return true; + } + } + + // Given a mouse event, find the corresponding position. If liberal + // is false, it checks whether a gutter or scrollbar was clicked, + // and returns null if it was. forRect is used by rectangular + // selections, and tries to estimate a character position even for + // coordinates beyond the right of the text. + function posFromMouse(cm, e, liberal, forRect) { + var display = cm.display; + if (!liberal && e_target(e).getAttribute("cm-not-content") == "true") return null; + + var x, y, space = display.lineSpace.getBoundingClientRect(); + // Fails unpredictably on IE[67] when mouse is dragged around quickly. + try { x = e.clientX - space.left; y = e.clientY - space.top; } + catch (e) { return null; } + var coords = coordsChar(cm, x, y), line; + if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { + var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); + } + return coords; + } + + // A mouse down can be a single click, double click, triple click, + // start of selection drag, start of text drag, new cursor + // (ctrl-click), rectangle drag (alt-drag), or xwin + // middle-click-paste. Or it might be a click on something we should + // not interfere with, such as a scrollbar or widget. + function onMouseDown(e) { + var cm = this, display = cm.display; + if (display.activeTouch && display.input.supportsTouch() || signalDOMEvent(cm, e)) return; + display.shift = e.shiftKey; + + if (eventInWidget(display, e)) { + if (!webkit) { + // Briefly turn off draggability, to allow widgets to do + // normal dragging things. + display.scroller.draggable = false; + setTimeout(function(){display.scroller.draggable = true;}, 100); + } + return; + } + if (clickInGutter(cm, e)) return; + var start = posFromMouse(cm, e); + window.focus(); + + switch (e_button(e)) { + case 1: + if (start) + leftButtonDown(cm, e, start); + else if (e_target(e) == display.scroller) + e_preventDefault(e); + break; + case 2: + if (webkit) cm.state.lastMiddleDown = +new Date; + if (start) extendSelection(cm.doc, start); + setTimeout(function() {display.input.focus();}, 20); + e_preventDefault(e); + break; + case 3: + if (captureRightClick) onContextMenu(cm, e); + else delayBlurEvent(cm); + break; + } + } + + var lastClick, lastDoubleClick; + function leftButtonDown(cm, e, start) { + if (ie) setTimeout(bind(ensureFocus, cm), 0); + else cm.curOp.focus = activeElt(); + + var now = +new Date, type; + if (lastDoubleClick && lastDoubleClick.time > now - 400 && cmp(lastDoubleClick.pos, start) == 0) { + type = "triple"; + } else if (lastClick && lastClick.time > now - 400 && cmp(lastClick.pos, start) == 0) { + type = "double"; + lastDoubleClick = {time: now, pos: start}; + } else { + type = "single"; + lastClick = {time: now, pos: start}; + } + + var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained; + if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && + type == "single" && (contained = sel.contains(start)) > -1 && + !sel.ranges[contained].empty()) + leftButtonStartDrag(cm, e, start, modifier); + else + leftButtonSelect(cm, e, start, type, modifier); + } + + // Start a text drag. When it ends, see if any dragging actually + // happen, and treat as a click if it didn't. + function leftButtonStartDrag(cm, e, start, modifier) { + var display = cm.display, startTime = +new Date; + var dragEnd = operation(cm, function(e2) { + if (webkit) display.scroller.draggable = false; + cm.state.draggingText = false; + off(document, "mouseup", dragEnd); + off(display.scroller, "drop", dragEnd); + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { + e_preventDefault(e2); + if (!modifier && +new Date - 200 < startTime) + extendSelection(cm.doc, start); + // Work around unexplainable focus problem in IE9 (#2127) and Chrome (#3081) + if (webkit || ie && ie_version == 9) + setTimeout(function() {document.body.focus(); display.input.focus();}, 20); + else + display.input.focus(); + } + }); + // Let the drag handler handle this. + if (webkit) display.scroller.draggable = true; + cm.state.draggingText = dragEnd; + // IE's approach to draggable + if (display.scroller.dragDrop) display.scroller.dragDrop(); + on(document, "mouseup", dragEnd); + on(display.scroller, "drop", dragEnd); + } + + // Normal selection, as opposed to text dragging. + function leftButtonSelect(cm, e, start, type, addNew) { + var display = cm.display, doc = cm.doc; + e_preventDefault(e); + + var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; + if (addNew && !e.shiftKey) { + ourIndex = doc.sel.contains(start); + if (ourIndex > -1) + ourRange = ranges[ourIndex]; + else + ourRange = new Range(start, start); + } else { + ourRange = doc.sel.primary(); + ourIndex = doc.sel.primIndex; + } + + if (e.altKey) { + type = "rect"; + if (!addNew) ourRange = new Range(start, start); + start = posFromMouse(cm, e, true, true); + ourIndex = -1; + } else if (type == "double") { + var word = cm.findWordAt(start); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, word.anchor, word.head); + else + ourRange = word; + } else if (type == "triple") { + var line = new Range(Pos(start.line, 0), clipPos(doc, Pos(start.line + 1, 0))); + if (cm.display.shift || doc.extend) + ourRange = extendRange(doc, ourRange, line.anchor, line.head); + else + ourRange = line; + } else { + ourRange = extendRange(doc, ourRange, start); + } + + if (!addNew) { + ourIndex = 0; + setSelection(doc, new Selection([ourRange], 0), sel_mouse); + startSel = doc.sel; + } else if (ourIndex == -1) { + ourIndex = ranges.length; + setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), + {scroll: false, origin: "*mouse"}); + } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) { + setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0)); + startSel = doc.sel; + } else { + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); + } + + var lastPos = start; + function extendTo(pos) { + if (cmp(lastPos, pos) == 0) return; + lastPos = pos; + + if (type == "rect") { + var ranges = [], tabSize = cm.options.tabSize; + var startCol = countColumn(getLine(doc, start.line).text, start.ch, tabSize); + var posCol = countColumn(getLine(doc, pos.line).text, pos.ch, tabSize); + var left = Math.min(startCol, posCol), right = Math.max(startCol, posCol); + for (var line = Math.min(start.line, pos.line), end = Math.min(cm.lastLine(), Math.max(start.line, pos.line)); + line <= end; line++) { + var text = getLine(doc, line).text, leftPos = findColumn(text, left, tabSize); + if (left == right) + ranges.push(new Range(Pos(line, leftPos), Pos(line, leftPos))); + else if (text.length > leftPos) + ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); + } + if (!ranges.length) ranges.push(new Range(start, start)); + setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), + {origin: "*mouse", scroll: false}); + cm.scrollIntoView(pos); + } else { + var oldRange = ourRange; + var anchor = oldRange.anchor, head = pos; + if (type != "single") { + if (type == "double") + var range = cm.findWordAt(pos); + else + var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0))); + if (cmp(range.anchor, anchor) > 0) { + head = range.head; + anchor = minPos(oldRange.from(), range.anchor); + } else { + head = range.anchor; + anchor = maxPos(oldRange.to(), range.head); + } + } + var ranges = startSel.ranges.slice(0); + ranges[ourIndex] = new Range(clipPos(doc, anchor), head); + setSelection(doc, normalizeSelection(ranges, ourIndex), sel_mouse); + } + } + + var editorSize = display.wrapper.getBoundingClientRect(); + // Used to ensure timeout re-tries don't fire when another extend + // happened in the meantime (clearTimeout isn't reliable -- at + // least on Chrome, the timeouts still happen even when cleared, + // if the clear happens after their scheduled firing time). + var counter = 0; + + function extend(e) { + var curCount = ++counter; + var cur = posFromMouse(cm, e, true, type == "rect"); + if (!cur) return; + if (cmp(cur, lastPos) != 0) { + cm.curOp.focus = activeElt(); + extendTo(cur); + var visible = visibleLines(display, doc); + if (cur.line >= visible.to || cur.line < visible.from) + setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); + } else { + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; + if (outside) setTimeout(operation(cm, function() { + if (counter != curCount) return; + display.scroller.scrollTop += outside; + extend(e); + }), 50); + } + } + + function done(e) { + counter = Infinity; + e_preventDefault(e); + display.input.focus(); + off(document, "mousemove", move); + off(document, "mouseup", up); + doc.history.lastSelOrigin = null; + } + + var move = operation(cm, function(e) { + if (!e_button(e)) done(e); + else extend(e); + }); + var up = operation(cm, done); + on(document, "mousemove", move); + on(document, "mouseup", up); + } + + // Determines whether an event happened in the gutter, and fires the + // handlers for the corresponding event. + function gutterEvent(cm, e, type, prevent, signalfn) { + try { var mX = e.clientX, mY = e.clientY; } + catch(e) { return false; } + if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false; + if (prevent) e_preventDefault(e); + + var display = cm.display; + var lineBox = display.lineDiv.getBoundingClientRect(); + + if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e); + mY -= lineBox.top - display.viewOffset; + + for (var i = 0; i < cm.options.gutters.length; ++i) { + var g = display.gutters.childNodes[i]; + if (g && g.getBoundingClientRect().right >= mX) { + var line = lineAtHeight(cm.doc, mY); + var gutter = cm.options.gutters[i]; + signalfn(cm, type, cm, line, gutter, e); + return e_defaultPrevented(e); + } + } + } + + function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true, signalLater); + } + + // Kludge to work around strange IE behavior where it'll sometimes + // re-fire a series of drag-related events right after the drop (#1551) + var lastDrop = 0; + + function onDrop(e) { + var cm = this; + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + return; + e_preventDefault(e); + if (ie) lastDrop = +new Date; + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; + if (!pos || isReadOnly(cm)) return; + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + var n = files.length, text = Array(n), read = 0; + var loadFile = function(file, i) { + var reader = new FileReader; + reader.onload = operation(cm, function() { + text[i] = reader.result; + if (++read == n) { + pos = clipPos(cm.doc, pos); + var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}; + makeChange(cm.doc, change); + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); + } + }); + reader.readAsText(file); + }; + for (var i = 0; i < n; ++i) loadFile(files[i], i); + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e); + // Ensure the editor is re-focused + setTimeout(function() {cm.display.input.focus();}, 20); + return; + } + try { + var text = e.dataTransfer.getData("Text"); + if (text) { + if (cm.state.draggingText && !(mac ? e.altKey : e.ctrlKey)) + var selected = cm.listSelections(); + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); + if (selected) for (var i = 0; i < selected.length; ++i) + replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag"); + cm.replaceSelection(text, "around", "paste"); + cm.display.input.focus(); + } + } + catch(e){} + } + } + + function onDragStart(cm, e) { + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; + + e.dataTransfer.setData("Text", cm.getSelection()); + + // Use dummy image instead of default browsers image. + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. + if (e.dataTransfer.setDragImage && !safari) { + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + if (presto) { + img.width = img.height = 1; + cm.display.wrapper.appendChild(img); + // Force a relayout, or Opera won't use our image for some obscure reason + img._top = img.offsetTop; + } + e.dataTransfer.setDragImage(img, 0, 0); + if (presto) img.parentNode.removeChild(img); + } + } + + // SCROLL EVENTS + + // Sync the scrollable area and scrollbars, ensure the viewport + // covers the visible area. + function setScrollTop(cm, val) { + if (Math.abs(cm.doc.scrollTop - val) < 2) return; + cm.doc.scrollTop = val; + if (!gecko) updateDisplaySimple(cm, {top: val}); + if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; + cm.display.scrollbars.setScrollTop(val); + if (gecko) updateDisplaySimple(cm); + startWorker(cm, 100); + } + // Sync scroller and scrollbar, ensure the gutter elements are + // aligned. + function setScrollLeft(cm, val, isScroller) { + if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); + cm.doc.scrollLeft = val; + alignHorizontally(cm); + if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; + cm.display.scrollbars.setScrollLeft(val); + } + + // Since the delta values reported on mouse wheel events are + // unstandardized between browsers and even browser versions, and + // generally horribly unpredictable, this code starts by measuring + // the scroll effect that the first few mouse wheel events have, + // and, from that, detects the way it can convert deltas to pixel + // offsets afterwards. + // + // The reason we want to know the amount a wheel event will scroll + // is that it gives us a chance to update the display before the + // actual scrolling happens, reducing flickering. + + var wheelSamples = 0, wheelPixelsPerUnit = null; + // Fill in a browser-detected starting value on browsers where we + // know one. These don't have to be accurate -- the result of them + // being wrong would just be a slight flicker on the first wheel + // scroll (if it is large enough). + if (ie) wheelPixelsPerUnit = -.53; + else if (gecko) wheelPixelsPerUnit = 15; + else if (chrome) wheelPixelsPerUnit = -.7; + else if (safari) wheelPixelsPerUnit = -1/3; + + var wheelEventDelta = function(e) { + var dx = e.wheelDeltaX, dy = e.wheelDeltaY; + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; + else if (dy == null) dy = e.wheelDelta; + return {x: dx, y: dy}; + }; + CodeMirror.wheelEventPixels = function(e) { + var delta = wheelEventDelta(e); + delta.x *= wheelPixelsPerUnit; + delta.y *= wheelPixelsPerUnit; + return delta; + }; + + function onScrollWheel(cm, e) { + var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; + + var display = cm.display, scroll = display.scroller; + // Quit if there's nothing to scroll here + if (!(dx && scroll.scrollWidth > scroll.clientWidth || + dy && scroll.scrollHeight > scroll.clientHeight)) return; + + // Webkit browsers on OS X abort momentum scrolls when the target + // of the scroll event is removed from the scrollable element. + // This hack (see related code in patchDisplay) makes sure the + // element is kept around. + if (dy && mac && webkit) { + outer: for (var cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (var i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur; + break outer; + } + } + } + } + + // On some browsers, horizontal scrolling will cause redraws to + // happen before the gutter has been realigned, causing it to + // wriggle around in a most unseemly way. When we have an + // estimated pixels/delta value, we just handle horizontal + // scrolling entirely here. It'll be slightly off from native, but + // better than glitching out. + if (dx && !gecko && !presto && wheelPixelsPerUnit != null) { + if (dy) + setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); + setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); + e_preventDefault(e); + display.wheelStartX = null; // Abort measurement, if in progress + return; + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + var pixels = dy * wheelPixelsPerUnit; + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; + if (pixels < 0) top = Math.max(0, top + pixels - 50); + else bot = Math.min(cm.doc.height, bot + pixels + 50); + updateDisplaySimple(cm, {top: top, bottom: bot}); + } + + if (wheelSamples < 20) { + if (display.wheelStartX == null) { + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; + display.wheelDX = dx; display.wheelDY = dy; + setTimeout(function() { + if (display.wheelStartX == null) return; + var movedX = scroll.scrollLeft - display.wheelStartX; + var movedY = scroll.scrollTop - display.wheelStartY; + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || + (movedX && display.wheelDX && movedX / display.wheelDX); + display.wheelStartX = display.wheelStartY = null; + if (!sample) return; + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); + ++wheelSamples; + }, 200); + } else { + display.wheelDX += dx; display.wheelDY += dy; + } + } + } + + // KEY EVENTS + + // Run a handler that was bound to a key. + function doHandleBinding(cm, bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) return false; + } + // Ensure previous input has been read, so that the handler sees a + // consistent view of the document + cm.display.input.ensurePolled(); + var prevShift = cm.display.shift, done = false; + try { + if (isReadOnly(cm)) cm.state.suppressEdits = true; + if (dropShift) cm.display.shift = false; + done = bound(cm) != Pass; + } finally { + cm.display.shift = prevShift; + cm.state.suppressEdits = false; + } + return done; + } + + function lookupKeyForEditor(cm, name, handle) { + for (var i = 0; i < cm.state.keyMaps.length; i++) { + var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); + if (result) return result; + } + return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) + || lookupKey(name, cm.options.keyMap, handle, cm); + } + + var stopSeq = new Delayed; + function dispatchKey(cm, name, e, handle) { + var seq = cm.state.keySeq; + if (seq) { + if (isModifierKey(name)) return "handled"; + stopSeq.set(50, function() { + if (cm.state.keySeq == seq) { + cm.state.keySeq = null; + cm.display.input.reset(); + } + }); + name = seq + " " + name; + } + var result = lookupKeyForEditor(cm, name, handle); + + if (result == "multi") + cm.state.keySeq = name; + if (result == "handled") + signalLater(cm, "keyHandled", cm, name, e); + + if (result == "handled" || result == "multi") { + e_preventDefault(e); + restartBlink(cm); + } + + if (seq && !result && /\'$/.test(name)) { + e_preventDefault(e); + return true; + } + return !!result; + } + + // Handle a key from the keydown event. + function handleKeyBinding(cm, e) { + var name = keyName(e, true); + if (!name) return false; + + if (e.shiftKey && !cm.state.keySeq) { + // First try to resolve full name (including 'Shift-'). Failing + // that, see if there is a cursor-motion command (starting with + // 'go') bound to the keyname without 'Shift-'. + return dispatchKey(cm, "Shift-" + name, e, function(b) {return doHandleBinding(cm, b, true);}) + || dispatchKey(cm, name, e, function(b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + return doHandleBinding(cm, b); + }); + } else { + return dispatchKey(cm, name, e, function(b) { return doHandleBinding(cm, b); }); + } + } + + // Handle a key from the keypress event + function handleCharBinding(cm, e, ch) { + return dispatchKey(cm, "'" + ch + "'", e, + function(b) { return doHandleBinding(cm, b, true); }); + } + + var lastStoppedKey = null; + function onKeyDown(e) { + var cm = this; + cm.curOp.focus = activeElt(); + if (signalDOMEvent(cm, e)) return; + // IE does strange things with escape. + if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false; + var code = e.keyCode; + cm.display.shift = code == 16 || e.shiftKey; + var handled = handleKeyBinding(cm, e); + if (presto) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) + cm.replaceSelection("", null, "cut"); + } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + showCrossHair(cm); + } + + function showCrossHair(cm) { + var lineDiv = cm.display.lineDiv; + addClass(lineDiv, "CodeMirror-crosshair"); + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair"); + off(document, "keyup", up); + off(document, "mouseover", up); + } + } + on(document, "keyup", up); + on(document, "mouseover", up); + } + + function onKeyUp(e) { + if (e.keyCode == 16) this.doc.sel.shift = false; + signalDOMEvent(this, e); + } + + function onKeyPress(e) { + var cm = this; + if (eventInWidget(cm.display, e) || signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return; + var keyCode = e.keyCode, charCode = e.charCode; + if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if ((presto && (!e.which || e.which < 10)) && handleKeyBinding(cm, e)) return; + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (handleCharBinding(cm, e, ch)) return; + cm.display.input.onKeyPress(e); + } + + // FOCUS/BLUR EVENTS + + function delayBlurEvent(cm) { + cm.state.delayingBlurEvent = true; + setTimeout(function() { + if (cm.state.delayingBlurEvent) { + cm.state.delayingBlurEvent = false; + onBlur(cm); + } + }, 100); + } + + function onFocus(cm) { + if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false; + + if (cm.options.readOnly == "nocursor") return; + if (!cm.state.focused) { + signal(cm, "focus", cm); + cm.state.focused = true; + addClass(cm.display.wrapper, "CodeMirror-focused"); + // This test prevents this from firing when a context + // menu is closed (since the input reset would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { + cm.display.input.reset(); + if (webkit) setTimeout(function() { cm.display.input.reset(true); }, 20); // Issue #1730 + } + cm.display.input.receivedFocus(); + } + restartBlink(cm); + } + function onBlur(cm) { + if (cm.state.delayingBlurEvent) return; + + if (cm.state.focused) { + signal(cm, "blur", cm); + cm.state.focused = false; + rmClass(cm.display.wrapper, "CodeMirror-focused"); + } + clearInterval(cm.display.blinker); + setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150); + } + + // CONTEXT MENU HANDLING + + // To make the context menu work, we need to briefly unhide the + // textarea (making it as unobtrusive as possible) to let the + // right-click take effect on it. + function onContextMenu(cm, e) { + if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return; + cm.display.input.onContextMenu(e); + } + + function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) return false; + return gutterEvent(cm, e, "gutterContextMenu", false, signal); + } + + // UPDATING + + // Compute the position of the end of a change (its 'to' property + // refers to the pre-change end). + var changeEnd = CodeMirror.changeEnd = function(change) { + if (!change.text) return change.to; + return Pos(change.from.line + change.text.length - 1, + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); + }; + + // Adjust a position to refer to the post-change position of the + // same text, or the end of the change if the change covers it. + function adjustForChange(pos, change) { + if (cmp(pos, change.from) < 0) return pos; + if (cmp(pos, change.to) <= 0) return changeEnd(change); + + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; + if (pos.line == change.to.line) ch += changeEnd(change).ch - change.to.ch; + return Pos(line, ch); + } + + function computeSelAfterChange(doc, change) { + var out = []; + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + out.push(new Range(adjustForChange(range.anchor, change), + adjustForChange(range.head, change))); + } + return normalizeSelection(out, doc.sel.primIndex); + } + + function offsetPos(pos, old, nw) { + if (pos.line == old.line) + return Pos(nw.line, pos.ch - old.ch + nw.ch); + else + return Pos(nw.line + (pos.line - old.line), pos.ch); + } + + // Used by replaceSelections to allow moving the selection to the + // start or around the replaced test. Hint may be "start" or "around". + function computeReplacedSel(doc, changes, hint) { + var out = []; + var oldPrev = Pos(doc.first, 0), newPrev = oldPrev; + for (var i = 0; i < changes.length; i++) { + var change = changes[i]; + var from = offsetPos(change.from, oldPrev, newPrev); + var to = offsetPos(changeEnd(change), oldPrev, newPrev); + oldPrev = change.to; + newPrev = to; + if (hint == "around") { + var range = doc.sel.ranges[i], inv = cmp(range.head, range.anchor) < 0; + out[i] = new Range(inv ? to : from, inv ? from : to); + } else { + out[i] = new Range(from, from); + } + } + return new Selection(out, doc.sel.primIndex); + } + + // Allow "beforeChange" event handlers to influence a change + function filterChange(doc, change, update) { + var obj = { + canceled: false, + from: change.from, + to: change.to, + text: change.text, + origin: change.origin, + cancel: function() { this.canceled = true; } + }; + if (update) obj.update = function(from, to, text, origin) { + if (from) this.from = clipPos(doc, from); + if (to) this.to = clipPos(doc, to); + if (text) this.text = text; + if (origin !== undefined) this.origin = origin; + }; + signal(doc, "beforeChange", doc, obj); + if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); + + if (obj.canceled) return null; + return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; + } + + // Apply a change to a document, and add it to the document's + // history, and propagating it to all linked documents. + function makeChange(doc, change, ignoreReadOnly) { + if (doc.cm) { + if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, ignoreReadOnly); + if (doc.cm.state.suppressEdits) return; + } + + if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { + change = filterChange(doc, change, true); + if (!change) return; + } + + // Possibly split or suppress the update based on the presence + // of read-only spans in its range. + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); + if (split) { + for (var i = split.length - 1; i >= 0; --i) + makeChangeInner(doc, {from: split[i].from, to: split[i].to, text: i ? [""] : change.text}); + } else { + makeChangeInner(doc, change); + } + } + + function makeChangeInner(doc, change) { + if (change.text.length == 1 && change.text[0] == "" && cmp(change.from, change.to) == 0) return; + var selAfter = computeSelAfterChange(doc, change); + addChangeToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); + + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); + var rebased = []; + + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); + }); + } + + // Revert a change stored in a document's history. + function makeChangeFromHistory(doc, type, allowSelectionOnly) { + if (doc.cm && doc.cm.state.suppressEdits) return; + + var hist = doc.history, event, selAfter = doc.sel; + var source = type == "undo" ? hist.done : hist.undone, dest = type == "undo" ? hist.undone : hist.done; + + // Verify that there is a useable event (so that ctrl-z won't + // needlessly clear selection events) + for (var i = 0; i < source.length; i++) { + event = source[i]; + if (allowSelectionOnly ? event.ranges && !event.equals(doc.sel) : !event.ranges) + break; + } + if (i == source.length) return; + hist.lastOrigin = hist.lastSelOrigin = null; + + for (;;) { + event = source.pop(); + if (event.ranges) { + pushSelectionToHistory(event, dest); + if (allowSelectionOnly && !event.equals(doc.sel)) { + setSelection(doc, event, {clearRedo: false}); + return; + } + selAfter = event; + } + else break; + } + + // Build up a reverse change object to add to the opposite history + // stack (redo when undoing, and vice versa). + var antiChanges = []; + pushSelectionToHistory(selAfter, dest); + dest.push({changes: antiChanges, generation: hist.generation}); + hist.generation = event.generation || ++hist.maxGeneration; + + var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); + + for (var i = event.changes.length - 1; i >= 0; --i) { + var change = event.changes[i]; + change.origin = type; + if (filter && !filterChange(doc, change, false)) { + source.length = 0; + return; + } + + antiChanges.push(historyChangeFromChange(doc, change)); + + var after = i ? computeSelAfterChange(doc, change) : lst(source); + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); + if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); + var rebased = []; + + // Propagate to the linked documents + linkedDocs(doc, function(doc, sharedHist) { + if (!sharedHist && indexOf(rebased, doc.history) == -1) { + rebaseHist(doc.history, change); + rebased.push(doc.history); + } + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); + }); + } + } + + // Sub-views need their line numbers shifted when text is added + // above or below them in the parent document. + function shiftDoc(doc, distance) { + if (distance == 0) return; + doc.first += distance; + doc.sel = new Selection(map(doc.sel.ranges, function(range) { + return new Range(Pos(range.anchor.line + distance, range.anchor.ch), + Pos(range.head.line + distance, range.head.ch)); + }), doc.sel.primIndex); + if (doc.cm) { + regChange(doc.cm, doc.first, doc.first - distance, distance); + for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) + regLineChange(doc.cm, l, "gutter"); + } + } + + // More lower-level change function, handling only a single document + // (not linked ones). + function makeChangeSingleDoc(doc, change, selAfter, spans) { + if (doc.cm && !doc.cm.curOp) + return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); + + if (change.to.line < doc.first) { + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); + return; + } + if (change.from.line > doc.lastLine()) return; + + // Clip the change to the size of this doc + if (change.from.line < doc.first) { + var shift = change.text.length - 1 - (doc.first - change.from.line); + shiftDoc(doc, shift); + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), + text: [lst(change.text)], origin: change.origin}; + } + var last = doc.lastLine(); + if (change.to.line > last) { + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), + text: [change.text[0]], origin: change.origin}; + } + + change.removed = getBetween(doc, change.from, change.to); + + if (!selAfter) selAfter = computeSelAfterChange(doc, change); + if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans); + else updateDoc(doc, change, spans); + setSelectionNoUndo(doc, selAfter, sel_dontScroll); + } + + // Handle the interaction of a change to a document with the editor + // that this document is part of. + function makeChangeSingleDocInEditor(cm, change, spans) { + var doc = cm.doc, display = cm.display, from = change.from, to = change.to; + + var recomputeMaxLength = false, checkWidthStart = from.line; + if (!cm.options.lineWrapping) { + checkWidthStart = lineNo(visualLine(getLine(doc, from.line))); + doc.iter(checkWidthStart, to.line + 1, function(line) { + if (line == display.maxLine) { + recomputeMaxLength = true; + return true; + } + }); + } + + if (doc.sel.contains(change.from, change.to) > -1) + signalCursorActivity(cm); + + updateDoc(doc, change, spans, estimateHeight(cm)); + + if (!cm.options.lineWrapping) { + doc.iter(checkWidthStart, from.line + change.text.length, function(line) { + var len = lineLength(line); + if (len > display.maxLineLength) { + display.maxLine = line; + display.maxLineLength = len; + display.maxLineChanged = true; + recomputeMaxLength = false; + } + }); + if (recomputeMaxLength) cm.curOp.updateMaxLine = true; + } + + // Adjust frontier, schedule worker + doc.frontier = Math.min(doc.frontier, from.line); + startWorker(cm, 400); + + var lendiff = change.text.length - (to.line - from.line) - 1; + // Remember that these lines changed, for updating the display + if (change.full) + regChange(cm); + else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + regLineChange(cm, from.line, "text"); + else + regChange(cm, from.line, to.line + 1, lendiff); + + var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); + if (changeHandler || changesHandler) { + var obj = { + from: from, to: to, + text: change.text, + removed: change.removed, + origin: change.origin + }; + if (changeHandler) signalLater(cm, "change", cm, obj); + if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); + } + cm.display.selForContextMenu = null; + } + + function replaceRange(doc, code, from, to, origin) { + if (!to) to = from; + if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; } + if (typeof code == "string") code = splitLines(code); + makeChange(doc, {from: from, to: to, text: code, origin: origin}); + } + + // SCROLLING THINGS INTO VIEW + + // If an editor sits on the top or bottom of the window, partially + // scrolled out of view, this ensures that the cursor is visible. + function maybeScrollWindow(cm, coords) { + if (signalDOMEvent(cm, "scrollCursorIntoView")) return; + + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; + if (coords.top + box.top < 0) doScroll = true; + else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; + if (doScroll != null && !phantom) { + var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " + + (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " + + (coords.bottom - coords.top + scrollGap(cm) + display.barHeight) + "px; left: " + + coords.left + "px; width: 2px;"); + cm.display.lineSpace.appendChild(scrollNode); + scrollNode.scrollIntoView(doScroll); + cm.display.lineSpace.removeChild(scrollNode); + } + } + + // Scroll a given position into view (immediately), verifying that + // it actually became visible (as line heights are accurately + // measured, the position of something may 'drift' during drawing). + function scrollPosIntoView(cm, pos, end, margin) { + if (margin == null) margin = 0; + for (var limit = 0; limit < 5; limit++) { + var changed = false, coords = cursorCoords(cm, pos); + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); + var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), + Math.min(coords.top, endCoords.top) - margin, + Math.max(coords.left, endCoords.left), + Math.max(coords.bottom, endCoords.bottom) + margin); + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; + if (scrollPos.scrollTop != null) { + setScrollTop(cm, scrollPos.scrollTop); + if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; + } + if (scrollPos.scrollLeft != null) { + setScrollLeft(cm, scrollPos.scrollLeft); + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; + } + if (!changed) break; + } + return coords; + } + + // Scroll a given set of coordinates into view (immediately). + function scrollIntoView(cm, x1, y1, x2, y2) { + var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); + if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); + if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); + } + + // Calculate a new scroll position needed to scroll the given + // rectangle into view. Returns an object with scrollTop and + // scrollLeft properties. When these are undefined, the + // vertical/horizontal position does not need to be adjusted. + function calculateScrollPos(cm, x1, y1, x2, y2) { + var display = cm.display, snapMargin = textHeight(cm.display); + if (y1 < 0) y1 = 0; + var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; + var screen = displayHeight(cm), result = {}; + if (y2 - y1 > screen) y2 = y1 + screen; + var docBottom = cm.doc.height + paddingVert(display); + var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; + if (y1 < screentop) { + result.scrollTop = atTop ? 0 : y1; + } else if (y2 > screentop + screen) { + var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); + if (newTop != screentop) result.scrollTop = newTop; + } + + var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; + var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0); + var tooWide = x2 - x1 > screenw; + if (tooWide) x2 = x1 + screenw; + if (x1 < 10) + result.scrollLeft = 0; + else if (x1 < screenleft) + result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); + else if (x2 > screenw + screenleft - 3) + result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw; + return result; + } + + // Store a relative adjustment to the scroll position in the current + // operation (to be applied when the operation finishes). + function addToScrollPos(cm, left, top) { + if (left != null || top != null) resolveScrollToPos(cm); + if (left != null) + cm.curOp.scrollLeft = (cm.curOp.scrollLeft == null ? cm.doc.scrollLeft : cm.curOp.scrollLeft) + left; + if (top != null) + cm.curOp.scrollTop = (cm.curOp.scrollTop == null ? cm.doc.scrollTop : cm.curOp.scrollTop) + top; + } + + // Make sure that at the end of the operation the current cursor is + // shown. + function ensureCursorVisible(cm) { + resolveScrollToPos(cm); + var cur = cm.getCursor(), from = cur, to = cur; + if (!cm.options.lineWrapping) { + from = cur.ch ? Pos(cur.line, cur.ch - 1) : cur; + to = Pos(cur.line, cur.ch + 1); + } + cm.curOp.scrollToPos = {from: from, to: to, margin: cm.options.cursorScrollMargin, isCursor: true}; + } + + // When an operation has its scrollToPos property set, and another + // scroll action is applied before the end of the operation, this + // 'simulates' scrolling that position into view in a cheap way, so + // that the effect of intermediate scroll commands is not ignored. + function resolveScrollToPos(cm) { + var range = cm.curOp.scrollToPos; + if (range) { + cm.curOp.scrollToPos = null; + var from = estimateCoords(cm, range.from), to = estimateCoords(cm, range.to); + var sPos = calculateScrollPos(cm, Math.min(from.left, to.left), + Math.min(from.top, to.top) - range.margin, + Math.max(from.right, to.right), + Math.max(from.bottom, to.bottom) + range.margin); + cm.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + } + + // API UTILITIES + + // Indent the given line. The how parameter can be "smart", + // "add"/null, "subtract", or "prev". When aggressive is false + // (typically set to true for forced single-line indents), empty + // lines are not indented, and places where the mode returns Pass + // are left alone. + function indentLine(cm, n, how, aggressive) { + var doc = cm.doc, state; + if (how == null) how = "add"; + if (how == "smart") { + // Fall back to "prev" when the mode doesn't have an indentation + // method. + if (!doc.mode.indent) how = "prev"; + else state = getStateBefore(cm, n); + } + + var tabSize = cm.options.tabSize; + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); + if (line.stateAfter) line.stateAfter = null; + var curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0; + how = "not"; + } else if (how == "smart") { + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass || indentation > 150) { + if (!aggressive) return; + how = "prev"; + } + } + if (how == "prev") { + if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); + else indentation = 0; + } else if (how == "add") { + indentation = curSpace + cm.options.indentUnit; + } else if (how == "subtract") { + indentation = curSpace - cm.options.indentUnit; + } else if (typeof how == "number") { + indentation = curSpace + how; + } + indentation = Math.max(0, indentation); + + var indentString = "", pos = 0; + if (cm.options.indentWithTabs) + for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} + if (pos < indentation) indentString += spaceStr(indentation - pos); + + if (indentString != curSpaceString) { + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + line.stateAfter = null; + return true; + } else { + // Ensure that, if the cursor was in the whitespace at the start + // of the line, it is moved to the end of that space. + for (var i = 0; i < doc.sel.ranges.length; i++) { + var range = doc.sel.ranges[i]; + if (range.head.line == n && range.head.ch < curSpaceString.length) { + var pos = Pos(n, curSpaceString.length); + replaceOneSelection(doc, i, new Range(pos, pos)); + break; + } + } + } + } + + // Utility for applying a change to a line by handle or number, + // returning the number and optionally registering the line as + // changed. + function changeLine(doc, handle, changeType, op) { + var no = handle, line = handle; + if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType); + return line; + } + + // Helper for deleting text near the selection(s), used to implement + // backspace, delete, and similar functionality. + function deleteNearSelection(cm, compute) { + var ranges = cm.doc.sel.ranges, kill = []; + // Build up a set of ranges to kill first, merging overlapping + // ranges. + for (var i = 0; i < ranges.length; i++) { + var toKill = compute(ranges[i]); + while (kill.length && cmp(toKill.from, lst(kill).to) <= 0) { + var replaced = kill.pop(); + if (cmp(replaced.from, toKill.from) < 0) { + toKill.from = replaced.from; + break; + } + } + kill.push(toKill); + } + // Next, remove those actual ranges. + runInOp(cm, function() { + for (var i = kill.length - 1; i >= 0; i--) + replaceRange(cm.doc, "", kill[i].from, kill[i].to, "+delete"); + ensureCursorVisible(cm); + }); + } + + // Used for horizontal relative motion. Dir is -1 or 1 (left or + // right), unit can be "char", "column" (like char, but doesn't + // cross line boundaries), "word" (across next word), or "group" (to + // the start of next group of word or non-word-non-whitespace + // chars). The visually param controls whether, in right-to-left + // text, direction 1 means to move towards the next index in the + // string, or towards the character to the right of the current + // position. The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosH(doc, pos, dir, unit, visually) { + var line = pos.line, ch = pos.ch, origDir = dir; + var lineObj = getLine(doc, line); + var possible = true; + function findNextLine() { + var l = line + dir; + if (l < doc.first || l >= doc.first + doc.size) return (possible = false); + line = l; + return lineObj = getLine(doc, l); + } + function moveOnce(boundToLine) { + var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); + if (next == null) { + if (!boundToLine && findNextLine()) { + if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); + else ch = dir < 0 ? lineObj.text.length : 0; + } else return (possible = false); + } else ch = next; + return true; + } + + if (unit == "char") moveOnce(); + else if (unit == "column") moveOnce(true); + else if (unit == "word" || unit == "group") { + var sawType = null, group = unit == "group"; + var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); + for (var first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) break; + var cur = lineObj.text.charAt(ch) || "\n"; + var type = isWordChar(cur, helper) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p"; + if (group && !first && !type) type = "s"; + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce();} + break; + } + + if (type) sawType = type; + if (dir > 0 && !moveOnce(!first)) break; + } + } + var result = skipAtomic(doc, Pos(line, ch), origDir, true); + if (!possible) result.hitSide = true; + return result; + } + + // For relative vertical movement. Dir may be -1 or 1. Unit can be + // "page" or "line". The resulting position will have a hitSide=true + // property if it reached the end of the document. + function findPosV(cm, pos, dir, unit) { + var doc = cm.doc, x = pos.left, y; + if (unit == "page") { + var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); + y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3; + } + for (;;) { + var target = coordsChar(cm, x, y); + if (!target.outside) break; + if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } + y += dir * 5; + } + return target; + } + + // EDITOR METHODS + + // The publicly visible API. Note that methodOp(f) means + // 'wrap f in an operation, performed on its `this` parameter'. + + // This is not the complete set of editor methods. Most of the + // methods defined on the Doc type are also injected into + // CodeMirror.prototype, for backwards compatibility and + // convenience. + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); this.display.input.focus();}, + + setOption: function(option, value) { + var options = this.options, old = options[option]; + if (options[option] == value && option != "mode") return; + options[option] = value; + if (optionHandlers.hasOwnProperty(option)) + operation(this, optionHandlers[option])(this, value, old); + }, + + getOption: function(option) {return this.options[option];}, + getDoc: function() {return this.doc;}, + + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)); + }, + removeKeyMap: function(map) { + var maps = this.state.keyMaps; + for (var i = 0; i < maps.length; ++i) + if (maps[i] == map || maps[i].name == map) { + maps.splice(i, 1); + return true; + } + }, + + addOverlay: methodOp(function(spec, options) { + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); + if (mode.startState) throw new Error("Overlays may not be stateful."); + this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); + this.state.modeGen++; + regChange(this); + }), + removeOverlay: methodOp(function(spec) { + var overlays = this.state.overlays; + for (var i = 0; i < overlays.length; ++i) { + var cur = overlays[i].modeSpec; + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1); + this.state.modeGen++; + regChange(this); + return; + } + } + }), + + indentLine: methodOp(function(n, dir, aggressive) { + if (typeof dir != "string" && typeof dir != "number") { + if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); + }), + indentSelection: methodOp(function(how) { + var ranges = this.doc.sel.ranges, end = -1; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (!range.empty()) { + var from = range.from(), to = range.to(); + var start = Math.max(end, from.line); + end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; + for (var j = start; j < end; ++j) + indentLine(this, j, how); + var newRanges = this.doc.sel.ranges; + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); + } else if (range.head.line > end) { + indentLine(this, range.head.line, how, true); + end = range.head.line; + if (i == this.doc.sel.primIndex) ensureCursorVisible(this); + } + } + }), + + // Fetch the parser token for a given character. Useful for hacks + // that want to inspect the mode state (say, for completion). + getTokenAt: function(pos, precise) { + return takeToken(this, pos, precise); + }, + + getLineTokens: function(line, precise) { + return takeToken(this, Pos(line), precise, true); + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos); + var styles = getLineStyles(this, getLine(this.doc, pos.line)); + var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; + var type; + if (ch == 0) type = styles[2]; + else for (;;) { + var mid = (before + after) >> 1; + if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; + else if (styles[mid * 2 + 1] < ch) before = mid + 1; + else { type = styles[mid * 2 + 2]; break; } + } + var cut = type ? type.indexOf("cm-overlay ") : -1; + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1); + }, + + getModeAt: function(pos) { + var mode = this.doc.mode; + if (!mode.innerMode) return mode; + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0]; + }, + + getHelpers: function(pos, type) { + var found = []; + if (!helpers.hasOwnProperty(type)) return found; + var help = helpers[type], mode = this.getModeAt(pos); + if (typeof mode[type] == "string") { + if (help[mode[type]]) found.push(help[mode[type]]); + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]]; + if (val) found.push(val); + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]); + } else if (help[mode.name]) { + found.push(help[mode.name]); + } + for (var i = 0; i < help._global.length; i++) { + var cur = help._global[i]; + if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) + found.push(cur.val); + } + return found; + }, + + getStateAfter: function(line, precise) { + var doc = this.doc; + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); + return getStateBefore(this, line + 1, precise); + }, + + cursorCoords: function(start, mode) { + var pos, range = this.doc.sel.primary(); + if (start == null) pos = range.head; + else if (typeof start == "object") pos = clipPos(this.doc, start); + else pos = start ? range.from() : range.to(); + return cursorCoords(this, pos, mode || "page"); + }, + + charCoords: function(pos, mode) { + return charCoords(this, clipPos(this.doc, pos), mode || "page"); + }, + + coordsChar: function(coords, mode) { + coords = fromCoordSystem(this, coords, mode || "page"); + return coordsChar(this, coords.left, coords.top); + }, + + lineAtHeight: function(height, mode) { + height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; + return lineAtHeight(this.doc, height + this.display.viewOffset); + }, + heightAtLine: function(line, mode) { + var end = false, lineObj; + if (typeof line == "number") { + var last = this.doc.first + this.doc.size - 1; + if (line < this.doc.first) line = this.doc.first; + else if (line > last) { line = last; end = true; } + lineObj = getLine(this.doc, line); + } else { + lineObj = line; + } + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page").top + + (end ? this.doc.height - heightAtLine(lineObj) : 0); + }, + + defaultTextHeight: function() { return textHeight(this.display); }, + defaultCharWidth: function() { return charWidth(this.display); }, + + setGutterMarker: methodOp(function(line, gutterID, value) { + return changeLine(this.doc, line, "gutter", function(line) { + var markers = line.gutterMarkers || (line.gutterMarkers = {}); + markers[gutterID] = value; + if (!value && isEmpty(markers)) line.gutterMarkers = null; + return true; + }); + }), + + clearGutter: methodOp(function(gutterID) { + var cm = this, doc = cm.doc, i = doc.first; + doc.iter(function(line) { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + line.gutterMarkers[gutterID] = null; + regLineChange(cm, i, "gutter"); + if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; + } + ++i; + }); + }), + + lineInfo: function(line) { + if (typeof line == "number") { + if (!isLine(this.doc, line)) return null; + var n = line; + line = getLine(this.doc, line); + if (!line) return null; + } else { + var n = lineNo(line); + if (n == null) return null; + } + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, + widgets: line.widgets}; + }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo};}, + + addWidget: function(pos, node, scroll, vert, horiz) { + var display = this.display; + pos = cursorCoords(this, clipPos(this.doc, pos)); + var top = pos.bottom, left = pos.left; + node.style.position = "absolute"; + node.setAttribute("cm-ignore-events", "true"); + this.display.input.setUneditable(node); + display.sizer.appendChild(node); + if (vert == "over") { + top = pos.top; + } else if (vert == "above" || vert == "near") { + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); + // Default to positioning above (if specified and possible); otherwise default to positioning below + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) + top = pos.top - node.offsetHeight; + else if (pos.bottom + node.offsetHeight <= vspace) + top = pos.bottom; + if (left + node.offsetWidth > hspace) + left = hspace - node.offsetWidth; + } + node.style.top = top + "px"; + node.style.left = node.style.right = ""; + if (horiz == "right") { + left = display.sizer.clientWidth - node.offsetWidth; + node.style.right = "0px"; + } else { + if (horiz == "left") left = 0; + else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; + node.style.left = left + "px"; + } + if (scroll) + scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: onKeyUp, + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + return commands[cmd](this); + }, + + triggerElectric: methodOp(function(text) { triggerElectric(this, text); }), + + findPosH: function(from, amount, unit, visually) { + var dir = 1; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + cur = findPosH(this.doc, cur, dir, unit, visually); + if (cur.hitSide) break; + } + return cur; + }, + + moveH: methodOp(function(dir, unit) { + var cm = this; + cm.extendSelectionsBy(function(range) { + if (cm.display.shift || cm.doc.extend || range.empty()) + return findPosH(cm.doc, range.head, dir, unit, cm.options.rtlMoveVisually); + else + return dir < 0 ? range.from() : range.to(); + }, sel_move); + }), + + deleteH: methodOp(function(dir, unit) { + var sel = this.doc.sel, doc = this.doc; + if (sel.somethingSelected()) + doc.replaceSelection("", null, "+delete"); + else + deleteNearSelection(this, function(range) { + var other = findPosH(doc, range.head, dir, unit, false); + return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other}; + }); + }), + + findPosV: function(from, amount, unit, goalColumn) { + var dir = 1, x = goalColumn; + if (amount < 0) { dir = -1; amount = -amount; } + for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { + var coords = cursorCoords(this, cur, "div"); + if (x == null) x = coords.left; + else coords.left = x; + cur = findPosV(this, coords, dir, unit); + if (cur.hitSide) break; + } + return cur; + }, + + moveV: methodOp(function(dir, unit) { + var cm = this, doc = this.doc, goals = []; + var collapse = !cm.display.shift && !doc.extend && doc.sel.somethingSelected(); + doc.extendSelectionsBy(function(range) { + if (collapse) + return dir < 0 ? range.from() : range.to(); + var headPos = cursorCoords(cm, range.head, "div"); + if (range.goalColumn != null) headPos.left = range.goalColumn; + goals.push(headPos.left); + var pos = findPosV(cm, headPos, dir, unit); + if (unit == "page" && range == doc.sel.primary()) + addToScrollPos(cm, null, charCoords(cm, pos, "div").top - headPos.top); + return pos; + }, sel_move); + if (goals.length) for (var i = 0; i < doc.sel.ranges.length; i++) + doc.sel.ranges[i].goalColumn = goals[i]; + }), + + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + var doc = this.doc, line = getLine(doc, pos.line).text; + var start = pos.ch, end = pos.ch; + if (line) { + var helper = this.getHelper(pos, "wordChars"); + if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; + var startChar = line.charAt(start); + var check = isWordChar(startChar, helper) + ? function(ch) { return isWordChar(ch, helper); } + : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} + : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; + while (start > 0 && check(line.charAt(start - 1))) --start; + while (end < line.length && check(line.charAt(end))) ++end; + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)); + }, + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) return; + if (this.state.overwrite = !this.state.overwrite) + addClass(this.display.cursorDiv, "CodeMirror-overwrite"); + else + rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); + + signal(this, "overwriteToggle", this, this.state.overwrite); + }, + hasFocus: function() { return this.display.input.getField() == activeElt(); }, + + scrollTo: methodOp(function(x, y) { + if (x != null || y != null) resolveScrollToPos(this); + if (x != null) this.curOp.scrollLeft = x; + if (y != null) this.curOp.scrollTop = y; + }), + getScrollInfo: function() { + var scroller = this.display.scroller; + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)}; + }, + + scrollIntoView: methodOp(function(range, margin) { + if (range == null) { + range = {from: this.doc.sel.primary().head, to: null}; + if (margin == null) margin = this.options.cursorScrollMargin; + } else if (typeof range == "number") { + range = {from: Pos(range, 0), to: null}; + } else if (range.from == null) { + range = {from: range, to: null}; + } + if (!range.to) range.to = range.from; + range.margin = margin || 0; + + if (range.from.line != null) { + resolveScrollToPos(this); + this.curOp.scrollToPos = range; + } else { + var sPos = calculateScrollPos(this, Math.min(range.from.left, range.to.left), + Math.min(range.from.top, range.to.top) - range.margin, + Math.max(range.from.right, range.to.right), + Math.max(range.from.bottom, range.to.bottom) + range.margin); + this.scrollTo(sPos.scrollLeft, sPos.scrollTop); + } + }), + + setSize: methodOp(function(width, height) { + var cm = this; + function interpret(val) { + return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; + } + if (width != null) cm.display.wrapper.style.width = interpret(width); + if (height != null) cm.display.wrapper.style.height = interpret(height); + if (cm.options.lineWrapping) clearLineMeasurementCache(this); + var lineNo = cm.display.viewFrom; + cm.doc.iter(lineNo, cm.display.viewTo, function(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) + if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; } + ++lineNo; + }); + cm.curOp.forceUpdate = true; + signal(cm, "refresh", this); + }), + + operation: function(f){return runInOp(this, f);}, + + refresh: methodOp(function() { + var oldHeight = this.display.cachedTextHeight; + regChange(this); + this.curOp.forceUpdate = true; + clearCaches(this); + this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop); + updateGutterSpace(this); + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) + estimateLineHeights(this); + signal(this, "refresh", this); + }), + + swapDoc: methodOp(function(doc) { + var old = this.doc; + old.cm = null; + attachDoc(this, doc); + clearCaches(this); + this.display.input.reset(); + this.scrollTo(doc.scrollLeft, doc.scrollTop); + this.curOp.forceScroll = true; + signalLater(this, "swapDoc", this, old); + return old; + }), + + getInputField: function(){return this.display.input.getField();}, + getWrapperElement: function(){return this.display.wrapper;}, + getScrollerElement: function(){return this.display.scroller;}, + getGutterElement: function(){return this.display.gutters;} + }; + eventMixin(CodeMirror); + + // OPTION DEFAULTS + + // The default configuration options. + var defaults = CodeMirror.defaults = {}; + // Functions to run when options are changed. + var optionHandlers = CodeMirror.optionHandlers = {}; + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt; + if (handle) optionHandlers[name] = + notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; + } + + // Passed to option handlers when there is no old value. + var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; + + // These two are, on init, called from the constructor because they + // have to be initialized before the editor can start at all. + option("value", "", function(cm, val) { + cm.setValue(val); + }, true); + option("mode", null, function(cm, val) { + cm.doc.modeOption = val; + loadMode(cm); + }, true); + + option("indentUnit", 2, loadMode, true); + option("indentWithTabs", false); + option("smartIndent", true); + option("tabSize", 4, function(cm) { + resetModeState(cm); + clearCaches(cm); + regChange(cm); + }, true); + option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) { + cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); + if (old != CodeMirror.Init) cm.refresh(); + }); + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true); + option("electricChars", true); + option("inputStyle", mobile ? "contenteditable" : "textarea", function() { + throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME + }, true); + option("rtlMoveVisually", !windows); + option("wholeLineUpdateBefore", true); + + option("theme", "default", function(cm) { + themeChanged(cm); + guttersChanged(cm); + }, true); + option("keyMap", "default", function(cm, val, old) { + var next = getKeyMap(val); + var prev = old != CodeMirror.Init && getKeyMap(old); + if (prev && prev.detach) prev.detach(cm, next); + if (next.attach) next.attach(cm, prev || null); + }); + option("extraKeys", null); + + option("lineWrapping", false, wrappingChanged, true); + option("gutters", [], function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("fixedGutter", true, function(cm, val) { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; + cm.refresh(); + }, true); + option("coverGutterNextToScrollbar", false, function(cm) {updateScrollbars(cm);}, true); + option("scrollbarStyle", "native", function(cm) { + initScrollbars(cm); + updateScrollbars(cm); + cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); + cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); + }, true); + option("lineNumbers", false, function(cm) { + setGuttersForLineNumbers(cm.options); + guttersChanged(cm); + }, true); + option("firstLineNumber", 1, guttersChanged, true); + option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); + option("showCursorWhenSelecting", false, updateSelection, true); + + option("resetSelectionOnContextMenu", true); + option("lineWiseCopyCut", true); + + option("readOnly", false, function(cm, val) { + if (val == "nocursor") { + onBlur(cm); + cm.display.input.blur(); + cm.display.disabled = true; + } else { + cm.display.disabled = false; + if (!val) cm.display.input.reset(); + } + }); + option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true); + option("dragDrop", true, dragDropChanged); + + option("cursorBlinkRate", 530); + option("cursorScrollMargin", 0); + option("cursorHeight", 1, updateSelection, true); + option("singleCursorHeightPerLine", true, updateSelection, true); + option("workTime", 100); + option("workDelay", 100); + option("flattenSpans", true, resetModeState, true); + option("addModeClass", false, resetModeState, true); + option("pollInterval", 100); + option("undoDepth", 200, function(cm, val){cm.doc.history.undoDepth = val;}); + option("historyEventDelay", 1250); + option("viewportMargin", 10, function(cm){cm.refresh();}, true); + option("maxHighlightLength", 10000, resetModeState, true); + option("moveInputWithCursor", true, function(cm, val) { + if (!val) cm.display.input.resetPosition(); + }); + + option("tabindex", null, function(cm, val) { + cm.display.input.getField().tabIndex = val || ""; + }); + option("autofocus", null); + + // MODE DEFINITION AND QUERYING + + // Known modes, by name and by MIME + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; + + // Extra arguments are stored as the mode's dependencies, which is + // used by (legacy) mechanisms like loadmode.js to automatically + // load a mode. (Preferred mechanism is the require/define calls.) + CodeMirror.defineMode = function(name, mode) { + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) + mode.dependencies = Array.prototype.slice.call(arguments, 2); + modes[name] = mode; + }; + + CodeMirror.defineMIME = function(mime, spec) { + mimeModes[mime] = spec; + }; + + // Given a MIME type, a {name, ...options} config object, or a name + // string, return a mode config object. + CodeMirror.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + var found = mimeModes[spec.name]; + if (typeof found == "string") found = {name: found}; + spec = createObj(found, spec); + spec.name = found.name; + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return CodeMirror.resolveMode("application/xml"); + } + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; + }; + + // Given a mode spec (anything that resolveMode accepts), find and + // initialize an actual mode object. + CodeMirror.getMode = function(options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + var modeObj = mfactory(options, spec); + if (modeExtensions.hasOwnProperty(spec.name)) { + var exts = modeExtensions[spec.name]; + for (var prop in exts) { + if (!exts.hasOwnProperty(prop)) continue; + if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; + modeObj[prop] = exts[prop]; + } + } + modeObj.name = spec.name; + if (spec.helperType) modeObj.helperType = spec.helperType; + if (spec.modeProps) for (var prop in spec.modeProps) + modeObj[prop] = spec.modeProps[prop]; + + return modeObj; + }; + + // Minimal default mode. + CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; + }); + CodeMirror.defineMIME("text/plain", "null"); + + // This can be used to attach properties to mode objects from + // outside the actual mode definition. + var modeExtensions = CodeMirror.modeExtensions = {}; + CodeMirror.extendMode = function(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + copyObj(properties, exts); + }; + + // EXTENSIONS + + CodeMirror.defineExtension = function(name, func) { + CodeMirror.prototype[name] = func; + }; + CodeMirror.defineDocExtension = function(name, func) { + Doc.prototype[name] = func; + }; + CodeMirror.defineOption = option; + + var initHooks = []; + CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; + + var helpers = CodeMirror.helpers = {}; + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []}; + helpers[type][name] = value; + }; + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value); + helpers[type]._global.push({pred: predicate, val: value}); + }; + + // MODE STATE HANDLING + + // Utility functions for working with state. Exported because nested + // modes need to do this for their inner modes. + + var copyState = CodeMirror.copyState = function(mode, state) { + if (state === true) return state; + if (mode.copyState) return mode.copyState(state); + var nstate = {}; + for (var n in state) { + var val = state[n]; + if (val instanceof Array) val = val.concat([]); + nstate[n] = val; + } + return nstate; + }; + + var startState = CodeMirror.startState = function(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true; + }; + + // Given a mode and a state (for that mode), find the inner mode and + // state at the position that the state refers to. + CodeMirror.innerMode = function(mode, state) { + while (mode.innerMode) { + var info = mode.innerMode(state); + if (!info || info.mode == mode) break; + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; + }; + + // STANDARD COMMANDS + + // Commands are parameter-less actions that can be performed on an + // editor, mostly used for keybindings. + var commands = CodeMirror.commands = { + selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);}, + singleSelection: function(cm) { + cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll); + }, + killLine: function(cm) { + deleteNearSelection(cm, function(range) { + if (range.empty()) { + var len = getLine(cm.doc, range.head.line).text.length; + if (range.head.ch == len && range.head.line < cm.lastLine()) + return {from: range.head, to: Pos(range.head.line + 1, 0)}; + else + return {from: range.head, to: Pos(range.head.line, len)}; + } else { + return {from: range.from(), to: range.to()}; + } + }); + }, + deleteLine: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), + to: clipPos(cm.doc, Pos(range.to().line + 1, 0))}; + }); + }, + delLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + return {from: Pos(range.from().line, 0), to: range.from()}; + }); + }, + delWrappedLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var leftPos = cm.coordsChar({left: 0, top: top}, "div"); + return {from: leftPos, to: range.from()}; + }); + }, + delWrappedLineRight: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + return {from: range.from(), to: rightPos }; + }); + }, + undo: function(cm) {cm.undo();}, + redo: function(cm) {cm.redo();}, + undoSelection: function(cm) {cm.undoSelection();}, + redoSelection: function(cm) {cm.redoSelection();}, + goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, + goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, + goLineStart: function(cm) { + cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); }, + {origin: "+move", bias: 1}); + }, + goLineStartSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + return lineStartSmart(cm, range.head); + }, {origin: "+move", bias: 1}); + }, + goLineEnd: function(cm) { + cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); }, + {origin: "+move", bias: -1}); + }, + goLineRight: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + }, sel_move); + }, + goLineLeft: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + return cm.coordsChar({left: 0, top: top}, "div"); + }, sel_move); + }, + goLineLeftSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var pos = cm.coordsChar({left: 0, top: top}, "div"); + if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head); + return pos; + }, sel_move); + }, + goLineUp: function(cm) {cm.moveV(-1, "line");}, + goLineDown: function(cm) {cm.moveV(1, "line");}, + goPageUp: function(cm) {cm.moveV(-1, "page");}, + goPageDown: function(cm) {cm.moveV(1, "page");}, + goCharLeft: function(cm) {cm.moveH(-1, "char");}, + goCharRight: function(cm) {cm.moveH(1, "char");}, + goColumnLeft: function(cm) {cm.moveH(-1, "column");}, + goColumnRight: function(cm) {cm.moveH(1, "column");}, + goWordLeft: function(cm) {cm.moveH(-1, "word");}, + goGroupRight: function(cm) {cm.moveH(1, "group");}, + goGroupLeft: function(cm) {cm.moveH(-1, "group");}, + goWordRight: function(cm) {cm.moveH(1, "word");}, + delCharBefore: function(cm) {cm.deleteH(-1, "char");}, + delCharAfter: function(cm) {cm.deleteH(1, "char");}, + delWordBefore: function(cm) {cm.deleteH(-1, "word");}, + delWordAfter: function(cm) {cm.deleteH(1, "word");}, + delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, + delGroupAfter: function(cm) {cm.deleteH(1, "group");}, + indentAuto: function(cm) {cm.indentSelection("smart");}, + indentMore: function(cm) {cm.indentSelection("add");}, + indentLess: function(cm) {cm.indentSelection("subtract");}, + insertTab: function(cm) {cm.replaceSelection("\t");}, + insertSoftTab: function(cm) { + var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].from(); + var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); + spaces.push(new Array(tabSize - col % tabSize + 1).join(" ")); + } + cm.replaceSelections(spaces); + }, + defaultTab: function(cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.execCommand("insertTab"); + }, + transposeChars: function(cm) { + runInOp(cm, function() { + var ranges = cm.listSelections(), newSel = []; + for (var i = 0; i < ranges.length; i++) { + var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; + if (line) { + if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1); + if (cur.ch > 0) { + cur = new Pos(cur.line, cur.ch + 1); + cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), + Pos(cur.line, cur.ch - 2), cur, "+transpose"); + } else if (cur.line > cm.doc.first) { + var prev = getLine(cm.doc, cur.line - 1).text; + if (prev) + cm.replaceRange(line.charAt(0) + "\n" + prev.charAt(prev.length - 1), + Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose"); + } + } + newSel.push(new Range(cur, cur)); + } + cm.setSelections(newSel); + }); + }, + newlineAndIndent: function(cm) { + runInOp(cm, function() { + var len = cm.listSelections().length; + for (var i = 0; i < len; i++) { + var range = cm.listSelections()[i]; + cm.replaceRange("\n", range.anchor, range.head, "+input"); + cm.indentLine(range.from().line + 1, null, true); + ensureCursorVisible(cm); + } + }); + }, + toggleOverwrite: function(cm) {cm.toggleOverwrite();} + }; + + + // STANDARD KEYMAPS + + var keyMap = CodeMirror.keyMap = {}; + + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite", + "Esc": "singleSelection" + }; + // Note that the save and find-related commands aren't defined by + // default. User code or addons can define them. Unknown commands + // are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + fallthrough: "basic" + }; + // Very basic readline/emacs-style bindings, which are standard on Mac. + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + fallthrough: ["basic", "emacsy"] + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + + // KEYMAP DISPATCH + + function normalizeKeyName(name) { + var parts = name.split(/-(?!$)/), name = parts[parts.length - 1]; + var alt, ctrl, shift, cmd; + for (var i = 0; i < parts.length - 1; i++) { + var mod = parts[i]; + if (/^(cmd|meta|m)$/i.test(mod)) cmd = true; + else if (/^a(lt)?$/i.test(mod)) alt = true; + else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true; + else if (/^s(hift)$/i.test(mod)) shift = true; + else throw new Error("Unrecognized modifier name: " + mod); + } + if (alt) name = "Alt-" + name; + if (ctrl) name = "Ctrl-" + name; + if (cmd) name = "Cmd-" + name; + if (shift) name = "Shift-" + name; + return name; + } + + // This is a kludge to keep keymaps mostly working as raw objects + // (backwards compatibility) while at the same time support features + // like normalization and multi-stroke key bindings. It compiles a + // new normalized keymap, and then updates the old object to reflect + // this. + CodeMirror.normalizeKeyMap = function(keymap) { + var copy = {}; + for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) { + var value = keymap[keyname]; + if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue; + if (value == "...") { delete keymap[keyname]; continue; } + + var keys = map(keyname.split(" "), normalizeKeyName); + for (var i = 0; i < keys.length; i++) { + var val, name; + if (i == keys.length - 1) { + name = keys.join(" "); + val = value; + } else { + name = keys.slice(0, i + 1).join(" "); + val = "..."; + } + var prev = copy[name]; + if (!prev) copy[name] = val; + else if (prev != val) throw new Error("Inconsistent bindings for " + name); + } + delete keymap[keyname]; + } + for (var prop in copy) keymap[prop] = copy[prop]; + return keymap; + }; + + var lookupKey = CodeMirror.lookupKey = function(key, map, handle, context) { + map = getKeyMap(map); + var found = map.call ? map.call(key, context) : map[key]; + if (found === false) return "nothing"; + if (found === "...") return "multi"; + if (found != null && handle(found)) return "handled"; + + if (map.fallthrough) { + if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") + return lookupKey(key, map.fallthrough, handle, context); + for (var i = 0; i < map.fallthrough.length; i++) { + var result = lookupKey(key, map.fallthrough[i], handle, context); + if (result) return result; + } + } + }; + + // Modifier key presses don't count as 'real' key presses for the + // purpose of keymap fallthrough. + var isModifierKey = CodeMirror.isModifierKey = function(value) { + var name = typeof value == "string" ? value : keyNames[value.keyCode]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + }; + + // Look up the name of a key as indicated by an event object. + var keyName = CodeMirror.keyName = function(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) return false; + var base = keyNames[event.keyCode], name = base; + if (name == null || event.altGraphKey) return false; + if (event.altKey && base != "Alt") name = "Alt-" + name; + if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name; + if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "Cmd-" + name; + if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name; + return name; + }; + + function getKeyMap(val) { + return typeof val == "string" ? keyMap[val] : val; + } + + // FROMTEXTAREA + + CodeMirror.fromTextArea = function(textarea, options) { + options = options ? copyObj(options) : {}; + options.value = textarea.value; + if (!options.tabindex && textarea.tabIndex) + options.tabindex = textarea.tabIndex; + if (!options.placeholder && textarea.placeholder) + options.placeholder = textarea.placeholder; + // Set autofocus to true if this textarea is focused, or if it has + // autofocus and no other element is focused. + if (options.autofocus == null) { + var hasFocus = activeElt(); + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body; + } + + function save() {textarea.value = cm.getValue();} + if (textarea.form) { + on(textarea.form, "submit", save); + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + var form = textarea.form, realSubmit = form.submit; + try { + var wrappedSubmit = form.submit = function() { + save(); + form.submit = realSubmit; + form.submit(); + form.submit = wrappedSubmit; + }; + } catch(e) {} + } + } + + options.finishInit = function(cm) { + cm.save = save; + cm.getTextArea = function() { return textarea; }; + cm.toTextArea = function() { + cm.toTextArea = isNaN; // Prevent this from being ran twice + save(); + textarea.parentNode.removeChild(cm.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + off(textarea.form, "submit", save); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + }; + + textarea.style.display = "none"; + var cm = CodeMirror(function(node) { + textarea.parentNode.insertBefore(node, textarea.nextSibling); + }, options); + return cm; + }; + + // STRING STREAM + + // Fed to the mode parsers, provides helper functions to make + // parsers more succinct. + + var StringStream = CodeMirror.StringStream = function(string, tabSize) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + this.lastColumnPos = this.lastColumnValue = 0; + this.lineStart = 0; + }; + + StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == this.lineStart;}, + peek: function() {return this.string.charAt(this.pos) || undefined;}, + next: function() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++); + }, + eat: function(match) { + var ch = this.string.charAt(this.pos); + if (typeof match == "string") var ok = ch == match; + else var ok = ch && (match.test ? match.test(ch) : match(ch)); + if (ok) {++this.pos; return ch;} + }, + eatWhile: function(match) { + var start = this.pos; + while (this.eat(match)){} + return this.pos > start; + }, + eatSpace: function() { + var start = this.pos; + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; + return this.pos > start; + }, + skipToEnd: function() {this.pos = this.string.length;}, + skipTo: function(ch) { + var found = this.string.indexOf(ch, this.pos); + if (found > -1) {this.pos = found; return true;} + }, + backUp: function(n) {this.pos -= n;}, + column: function() { + if (this.lastColumnPos < this.start) { + this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); + this.lastColumnPos = this.start; + } + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + indentation: function() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } else { + var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + } + }; + + // TEXTMARKERS + + // Created with markText and setBookmark methods. A TextMarker is a + // handle that can be used to clear or find a marked position in the + // document. Line objects hold arrays (markedSpans) containing + // {from, to, marker} object pointing to such marker objects, and + // indicating that such a marker is present on that line. Multiple + // lines may point to the same marker when it spans across lines. + // The spans will have null for their from/to properties when the + // marker continues beyond the start/end of the line. Markers have + // links back to the lines they currently touch. + + var nextMarkerId = 0; + + var TextMarker = CodeMirror.TextMarker = function(doc, type) { + this.lines = []; + this.type = type; + this.doc = doc; + this.id = ++nextMarkerId; + }; + eventMixin(TextMarker); + + // Clear the marker. + TextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + var cm = this.doc.cm, withOp = cm && !cm.curOp; + if (withOp) startOperation(cm); + if (hasHandler(this, "clear")) { + var found = this.find(); + if (found) signalLater(this, "clear", found.from, found.to); + } + var min = null, max = null; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (cm && !this.collapsed) regLineChange(cm, lineNo(line), "text"); + else if (cm) { + if (span.to != null) max = lineNo(line); + if (span.from != null) min = lineNo(line); + } + line.markedSpans = removeMarkedSpan(line.markedSpans, span); + if (span.from == null && this.collapsed && !lineIsHidden(this.doc, line) && cm) + updateLineHeight(line, textHeight(cm.display)); + } + if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { + var visual = visualLine(this.lines[i]), len = lineLength(visual); + if (len > cm.display.maxLineLength) { + cm.display.maxLine = visual; + cm.display.maxLineLength = len; + cm.display.maxLineChanged = true; + } + } + + if (min != null && cm && this.collapsed) regChange(cm, min, max + 1); + this.lines.length = 0; + this.explicitlyCleared = true; + if (this.atomic && this.doc.cantEdit) { + this.doc.cantEdit = false; + if (cm) reCheckSelection(cm.doc); + } + if (cm) signalLater(cm, "markerCleared", cm, this); + if (withOp) endOperation(cm); + if (this.parent) this.parent.clear(); + }; + + // Find the position of the marker in the document. Returns a {from, + // to} object by default. Side can be passed to get a specific side + // -- 0 (both), -1 (left), or 1 (right). When lineObj is true, the + // Pos objects returned contain a line object, rather than a line + // number (used to prevent looking up the same line twice). + TextMarker.prototype.find = function(side, lineObj) { + if (side == null && this.type == "bookmark") side = 1; + var from, to; + for (var i = 0; i < this.lines.length; ++i) { + var line = this.lines[i]; + var span = getMarkedSpanFor(line.markedSpans, this); + if (span.from != null) { + from = Pos(lineObj ? line : lineNo(line), span.from); + if (side == -1) return from; + } + if (span.to != null) { + to = Pos(lineObj ? line : lineNo(line), span.to); + if (side == 1) return to; + } + } + return from && {from: from, to: to}; + }; + + // Signals that the marker's widget changed, and surrounding layout + // should be recomputed. + TextMarker.prototype.changed = function() { + var pos = this.find(-1, true), widget = this, cm = this.doc.cm; + if (!pos || !cm) return; + runInOp(cm, function() { + var line = pos.line, lineN = lineNo(pos.line); + var view = findViewForLine(cm, lineN); + if (view) { + clearLineMeasurementCacheFor(view); + cm.curOp.selectionChanged = cm.curOp.forceUpdate = true; + } + cm.curOp.updateMaxLine = true; + if (!lineIsHidden(widget.doc, line) && widget.height != null) { + var oldHeight = widget.height; + widget.height = null; + var dHeight = widgetHeight(widget) - oldHeight; + if (dHeight) + updateLineHeight(line, line.height + dHeight); + } + }); + }; + + TextMarker.prototype.attachLine = function(line) { + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) + (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); + } + this.lines.push(line); + }; + TextMarker.prototype.detachLine = function(line) { + this.lines.splice(indexOf(this.lines, line), 1); + if (!this.lines.length && this.doc.cm) { + var op = this.doc.cm.curOp; + (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); + } + }; + + // Collapsed markers have unique ids, in order to be able to order + // them, which is needed for uniquely determining an outer marker + // when they overlap (they may nest, but not partially overlap). + var nextMarkerId = 0; + + // Create a marker, wire it up to the right lines, and + function markText(doc, from, to, options, type) { + // Shared markers (across linked documents) are handled separately + // (markTextShared will call out to this again, once per + // document). + if (options && options.shared) return markTextShared(doc, from, to, options, type); + // Ensure we are in an operation. + if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); + + var marker = new TextMarker(doc, type), diff = cmp(from, to); + if (options) copyObj(options, marker, false); + // Don't connect empty markers unless clearWhenEmpty is false + if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) + return marker; + if (marker.replacedWith) { + // Showing up as a widget implies collapsed (widget replaces text) + marker.collapsed = true; + marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget"); + if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true"); + if (options.insertLeft) marker.widgetNode.insertLeft = true; + } + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + throw new Error("Inserting collapsed marker partially overlapping an existing one"); + sawCollapsedSpans = true; + } + + if (marker.addToHistory) + addChangeToHistory(doc, {from: from, to: to, origin: "markText"}, doc.sel, NaN); + + var curLine = from.line, cm = doc.cm, updateMaxLine; + doc.iter(curLine, to.line + 1, function(line) { + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(line) == cm.display.maxLine) + updateMaxLine = true; + if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0); + addMarkedSpan(line, new MarkedSpan(marker, + curLine == from.line ? from.ch : null, + curLine == to.line ? to.ch : null)); + ++curLine; + }); + // lineIsHidden depends on the presence of the spans, so needs a second pass + if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { + if (lineIsHidden(doc, line)) updateLineHeight(line, 0); + }); + + if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); + + if (marker.readOnly) { + sawReadOnlySpans = true; + if (doc.history.done.length || doc.history.undone.length) + doc.clearHistory(); + } + if (marker.collapsed) { + marker.id = ++nextMarkerId; + marker.atomic = true; + } + if (cm) { + // Sync editor state + if (updateMaxLine) cm.curOp.updateMaxLine = true; + if (marker.collapsed) + regChange(cm, from.line, to.line + 1); + else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) + for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text"); + if (marker.atomic) reCheckSelection(cm.doc); + signalLater(cm, "markerAdded", cm, marker); + } + return marker; + } + + // SHARED TEXTMARKERS + + // A shared marker spans multiple linked documents. It is + // implemented as a meta-marker-object controlling multiple normal + // markers. + var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) { + this.markers = markers; + this.primary = primary; + for (var i = 0; i < markers.length; ++i) + markers[i].parent = this; + }; + eventMixin(SharedTextMarker); + + SharedTextMarker.prototype.clear = function() { + if (this.explicitlyCleared) return; + this.explicitlyCleared = true; + for (var i = 0; i < this.markers.length; ++i) + this.markers[i].clear(); + signalLater(this, "clear"); + }; + SharedTextMarker.prototype.find = function(side, lineObj) { + return this.primary.find(side, lineObj); + }; + + function markTextShared(doc, from, to, options, type) { + options = copyObj(options); + options.shared = false; + var markers = [markText(doc, from, to, options, type)], primary = markers[0]; + var widget = options.widgetNode; + linkedDocs(doc, function(doc) { + if (widget) options.widgetNode = widget.cloneNode(true); + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); + for (var i = 0; i < doc.linked.length; ++i) + if (doc.linked[i].isParent) return; + primary = lst(markers); + }); + return new SharedTextMarker(markers, primary); + } + + function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), + function(m) { return m.parent; }); + } + + function copySharedMarkers(doc, markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], pos = marker.find(); + var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); + if (cmp(mFrom, mTo)) { + var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); + marker.markers.push(subMark); + subMark.parent = marker; + } + } + } + + function detachSharedMarkers(markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], linked = [marker.primary.doc];; + linkedDocs(marker.primary.doc, function(d) { linked.push(d); }); + for (var j = 0; j < marker.markers.length; j++) { + var subMarker = marker.markers[j]; + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null; + marker.markers.splice(j--, 1); + } + } + } + } + + // TEXTMARKER SPANS + + function MarkedSpan(marker, from, to) { + this.marker = marker; + this.from = from; this.to = to; + } + + // Search an array of spans for a span matching the given marker. + function getMarkedSpanFor(spans, marker) { + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.marker == marker) return span; + } + } + // Remove a span from an array, returning undefined if no spans are + // left (we don't store arrays for lines without spans). + function removeMarkedSpan(spans, span) { + for (var r, i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]); + return r; + } + // Add a span to a line. + function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; + span.marker.attachLine(line); + } + + // Used for the algorithm that adjusts markers for a change in the + // document. These functions cut an array of spans at a given + // character position, returning an array of remaining chunks (or + // undefined if nothing remains). + function markedSpansBefore(old, startCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); + (nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)); + } + } + return nw; + } + function markedSpansAfter(old, endCh, isInsert) { + if (old) for (var i = 0, nw; i < old.length; ++i) { + var span = old[i], marker = span.marker; + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); + (nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)); + } + } + return nw; + } + + // Given a change object, compute the new set of marker spans that + // cover the line in which the change took place. Removes spans + // entirely within the change, reconnects spans belonging to the + // same marker that appear on both sides of the change, and cuts off + // spans partially within the change. Returns an array of span + // arrays with one element for each line in (after) the change. + function stretchSpansOverChange(doc, change) { + if (change.full) return null; + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; + if (!oldFirst && !oldLast) return null; + + var startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0; + // Get the spans that 'stick out' on both sides + var first = markedSpansBefore(oldFirst, startCh, isInsert); + var last = markedSpansAfter(oldLast, endCh, isInsert); + + // Next, merge those two ends + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); + if (first) { + // Fix up .to properties of first + for (var i = 0; i < first.length; ++i) { + var span = first[i]; + if (span.to == null) { + var found = getMarkedSpanFor(last, span.marker); + if (!found) span.to = startCh; + else if (sameLine) span.to = found.to == null ? null : found.to + offset; + } + } + } + if (last) { + // Fix up .from in last (or move them into first in case of sameLine) + for (var i = 0; i < last.length; ++i) { + var span = last[i]; + if (span.to != null) span.to += offset; + if (span.from == null) { + var found = getMarkedSpanFor(first, span.marker); + if (!found) { + span.from = offset; + if (sameLine) (first || (first = [])).push(span); + } + } else { + span.from += offset; + if (sameLine) (first || (first = [])).push(span); + } + } + } + // Make sure we didn't create any zero-length spans + if (first) first = clearEmptySpans(first); + if (last && last != first) last = clearEmptySpans(last); + + var newMarkers = [first]; + if (!sameLine) { + // Fill gap with whole-line-spans + var gap = change.text.length - 2, gapMarkers; + if (gap > 0 && first) + for (var i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)); + for (var i = 0; i < gap; ++i) + newMarkers.push(gapMarkers); + newMarkers.push(last); + } + return newMarkers; + } + + // Remove spans that are empty and don't have a clearWhenEmpty + // option of false. + function clearEmptySpans(spans) { + for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + spans.splice(i--, 1); + } + if (!spans.length) return null; + return spans; + } + + // Used for un/re-doing changes from the history. Combines the + // result of computing the existing spans with the set of spans that + // existed in the history (so that deleting around a span and then + // undoing brings back the span). + function mergeOldSpans(doc, change) { + var old = getOldSpans(doc, change); + var stretched = stretchSpansOverChange(doc, change); + if (!old) return stretched; + if (!stretched) return old; + + for (var i = 0; i < old.length; ++i) { + var oldCur = old[i], stretchCur = stretched[i]; + if (oldCur && stretchCur) { + spans: for (var j = 0; j < stretchCur.length; ++j) { + var span = stretchCur[j]; + for (var k = 0; k < oldCur.length; ++k) + if (oldCur[k].marker == span.marker) continue spans; + oldCur.push(span); + } + } else if (stretchCur) { + old[i] = stretchCur; + } + } + return old; + } + + // Used to 'clip' out readOnly ranges when making a change. + function removeReadOnlyRanges(doc, from, to) { + var markers = null; + doc.iter(from.line, to.line + 1, function(line) { + if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { + var mark = line.markedSpans[i].marker; + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + (markers || (markers = [])).push(mark); + } + }); + if (!markers) return null; + var parts = [{from: from, to: to}]; + for (var i = 0; i < markers.length; ++i) { + var mk = markers[i], m = mk.find(0); + for (var j = 0; j < parts.length; ++j) { + var p = parts[j]; + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue; + var newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to); + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + newParts.push({from: p.from, to: m.from}); + if (dto > 0 || !mk.inclusiveRight && !dto) + newParts.push({from: m.to, to: p.to}); + parts.splice.apply(parts, newParts); + j += newParts.length - 1; + } + } + return parts; + } + + // Connect or disconnect spans from a line. + function detachMarkedSpans(line) { + var spans = line.markedSpans; + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.detachLine(line); + line.markedSpans = null; + } + function attachMarkedSpans(line, spans) { + if (!spans) return; + for (var i = 0; i < spans.length; ++i) + spans[i].marker.attachLine(line); + line.markedSpans = spans; + } + + // Helpers used when computing which overlapping collapsed span + // counts as the larger one. + function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; } + function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; } + + // Returns a number indicating which of two overlapping collapsed + // spans is larger (and thus includes the other). Falls back to + // comparing ids when the spans cover exactly the same range. + function compareCollapsedMarkers(a, b) { + var lenDiff = a.lines.length - b.lines.length; + if (lenDiff != 0) return lenDiff; + var aPos = a.find(), bPos = b.find(); + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); + if (fromCmp) return -fromCmp; + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); + if (toCmp) return toCmp; + return b.id - a.id; + } + + // Find out whether a line ends or starts in a collapsed span. If + // so, return the marker for that span. + function collapsedSpanAtSide(line, start) { + var sps = sawCollapsedSpans && line.markedSpans, found; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + found = sp.marker; + } + return found; + } + function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); } + function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); } + + // Test whether there exists a collapsed span that partially + // overlaps (covers the start or end, but not both) of a new span. + // Such overlap is not allowed. + function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + var line = getLine(doc, lineNo); + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (!sp.marker.collapsed) continue; + var found = sp.marker.find(0); + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; + if (fromCmp <= 0 && (cmp(found.to, from) > 0 || (sp.marker.inclusiveRight && marker.inclusiveLeft)) || + fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft && marker.inclusiveRight))) + return true; + } + } + + // A visual line is a line as drawn on the screen. Folding, for + // example, can cause multiple logical lines to appear on the same + // visual line. This finds the start of the visual line that the + // given line is part of (usually that is the line itself). + function visualLine(line) { + var merged; + while (merged = collapsedSpanAtStart(line)) + line = merged.find(-1, true).line; + return line; + } + + // Returns an array of logical lines that continue the visual line + // started by the argument, or undefined if there are no such lines. + function visualLineContinued(line) { + var merged, lines; + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + (lines || (lines = [])).push(line); + } + return lines; + } + + // Get the line number of the start of the visual line that the + // given line number is part of. + function visualLineNo(doc, lineN) { + var line = getLine(doc, lineN), vis = visualLine(line); + if (line == vis) return lineN; + return lineNo(vis); + } + // Get the line number of the start of the next visual line after + // the given line. + function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) return lineN; + var line = getLine(doc, lineN), merged; + if (!lineIsHidden(doc, line)) return lineN; + while (merged = collapsedSpanAtEnd(line)) + line = merged.find(1, true).line; + return lineNo(line) + 1; + } + + // Compute whether a line is hidden. Lines count as hidden when they + // are part of a visual line that starts with another line, or when + // they are entirely covered by collapsed, non-widget span. + function lineIsHidden(doc, line) { + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var sp, i = 0; i < sps.length; ++i) { + sp = sps[i]; + if (!sp.marker.collapsed) continue; + if (sp.from == null) return true; + if (sp.marker.widgetNode) continue; + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + return true; + } + } + function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + var end = span.marker.find(1, true); + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)); + } + if (span.marker.inclusiveRight && span.to == line.text.length) + return true; + for (var sp, i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i]; + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) return true; + } + } + + // LINE WIDGETS + + // Line widgets are block elements displayed above or below a line. + + var LineWidget = CodeMirror.LineWidget = function(doc, node, options) { + if (options) for (var opt in options) if (options.hasOwnProperty(opt)) + this[opt] = options[opt]; + this.doc = doc; + this.node = node; + }; + eventMixin(LineWidget); + + function adjustScrollWhenAboveVisible(cm, line, diff) { + if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop)) + addToScrollPos(cm, null, diff); + } + + LineWidget.prototype.clear = function() { + var cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line); + if (no == null || !ws) return; + for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); + if (!ws.length) line.widgets = null; + var height = widgetHeight(this); + updateLineHeight(line, Math.max(0, line.height - height)); + if (cm) runInOp(cm, function() { + adjustScrollWhenAboveVisible(cm, line, -height); + regLineChange(cm, no, "widget"); + }); + }; + LineWidget.prototype.changed = function() { + var oldH = this.height, cm = this.doc.cm, line = this.line; + this.height = null; + var diff = widgetHeight(this) - oldH; + if (!diff) return; + updateLineHeight(line, line.height + diff); + if (cm) runInOp(cm, function() { + cm.curOp.forceUpdate = true; + adjustScrollWhenAboveVisible(cm, line, diff); + }); + }; + + function widgetHeight(widget) { + if (widget.height != null) return widget.height; + var cm = widget.doc.cm; + if (!cm) return 0; + if (!contains(document.body, widget.node)) { + var parentStyle = "position: relative;"; + if (widget.coverGutter) + parentStyle += "margin-left: -" + cm.display.gutters.offsetWidth + "px;"; + if (widget.noHScroll) + parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;"; + removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle)); + } + return widget.height = widget.node.offsetHeight; + } + + function addLineWidget(doc, handle, node, options) { + var widget = new LineWidget(doc, node, options); + var cm = doc.cm; + if (cm && widget.noHScroll) cm.display.alignWidgets = true; + changeLine(doc, handle, "widget", function(line) { + var widgets = line.widgets || (line.widgets = []); + if (widget.insertAt == null) widgets.push(widget); + else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); + widget.line = line; + if (cm && !lineIsHidden(doc, line)) { + var aboveVisible = heightAtLine(line) < doc.scrollTop; + updateLineHeight(line, line.height + widgetHeight(widget)); + if (aboveVisible) addToScrollPos(cm, null, widget.height); + cm.curOp.forceUpdate = true; + } + return true; + }); + return widget; + } + + // LINE DATA STRUCTURE + + // Line objects. These hold state related to a line, including + // highlighting info (the styles array). + var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { + this.text = text; + attachMarkedSpans(this, markedSpans); + this.height = estimateHeight ? estimateHeight(this) : 1; + }; + eventMixin(Line); + Line.prototype.lineNo = function() { return lineNo(this); }; + + // Change the content (text, markers) of a line. Automatically + // invalidates cached information and tries to re-estimate the + // line's height. + function updateLine(line, text, markedSpans, estimateHeight) { + line.text = text; + if (line.stateAfter) line.stateAfter = null; + if (line.styles) line.styles = null; + if (line.order != null) line.order = null; + detachMarkedSpans(line); + attachMarkedSpans(line, markedSpans); + var estHeight = estimateHeight ? estimateHeight(line) : 1; + if (estHeight != line.height) updateLineHeight(line, estHeight); + } + + // Detach a line from the document tree and its markers. + function cleanUpLine(line) { + line.parent = null; + detachMarkedSpans(line); + } + + function extractLineClasses(type, output) { + if (type) for (;;) { + var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); + if (!lineClass) break; + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (output[prop] == null) + output[prop] = lineClass[2]; + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + output[prop] += " " + lineClass[2]; + } + return type; + } + + function callBlankLine(mode, state) { + if (mode.blankLine) return mode.blankLine(state); + if (!mode.innerMode) return; + var inner = CodeMirror.innerMode(mode, state); + if (inner.mode.blankLine) return inner.mode.blankLine(inner.state); + } + + function readToken(mode, stream, state, inner) { + for (var i = 0; i < 10; i++) { + if (inner) inner[0] = CodeMirror.innerMode(mode, state).mode; + var style = mode.token(stream, state); + if (stream.pos > stream.start) return style; + } + throw new Error("Mode " + mode.name + " failed to advance stream."); + } + + // Utility for getTokenAt and getLineTokens + function takeToken(cm, pos, precise, asArray) { + function getObj(copy) { + return {start: stream.start, end: stream.pos, + string: stream.current(), + type: style || null, + state: copy ? copyState(doc.mode, state) : state}; + } + + var doc = cm.doc, mode = doc.mode, style; + pos = clipPos(doc, pos); + var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise); + var stream = new StringStream(line.text, cm.options.tabSize), tokens; + if (asArray) tokens = []; + while ((asArray || stream.pos < pos.ch) && !stream.eol()) { + stream.start = stream.pos; + style = readToken(mode, stream, state); + if (asArray) tokens.push(getObj(true)); + } + return asArray ? tokens : getObj(); + } + + // Run the given mode's parser over a line, calling f for each token. + function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { + var flattenSpans = mode.flattenSpans; + if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; + var curStart = 0, curStyle = null; + var stream = new StringStream(text, cm.options.tabSize), style; + var inner = cm.options.addModeClass && [null]; + if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses); + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false; + if (forceToEnd) processLine(cm, text, state, stream.pos); + stream.pos = text.length; + style = null; + } else { + style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses); + } + if (inner) { + var mName = inner[0].name; + if (mName) style = "m-" + (style ? mName + " " + style : mName); + } + if (!flattenSpans || curStyle != style) { + while (curStart < stream.start) { + curStart = Math.min(stream.start, curStart + 50000); + f(curStart, curStyle); + } + curStyle = style; + } + stream.start = stream.pos; + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 characters + var pos = Math.min(stream.pos, curStart + 50000); + f(pos, curStyle); + curStart = pos; + } + } + + // Compute a style array (an array starting with a mode generation + // -- for invalidation -- followed by pairs of end positions and + // style strings), which is used to highlight the tokens on the + // line. + function highlightLine(cm, line, state, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + var st = [cm.state.modeGen], lineClasses = {}; + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, state, function(end, style) { + st.push(end, style); + }, lineClasses, forceToEnd); + + // Run overlays, adjust style array. + for (var o = 0; o < cm.state.overlays.length; ++o) { + var overlay = cm.state.overlays[o], i = 1, at = 0; + runMode(cm, line.text, overlay.mode, true, function(end, style) { + var start = i; + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + var i_end = st[i]; + if (i_end > end) + st.splice(i, 1, end, st[i+1], i_end); + i += 2; + at = Math.min(end, i_end); + } + if (!style) return; + if (overlay.opaque) { + st.splice(start, i - start, end, "cm-overlay " + style); + i = start + 2; + } else { + for (; start < i; start += 2) { + var cur = st[start+1]; + st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style; + } + } + }, lineClasses); + } + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}; + } + + function getLineStyles(cm, line, updateFrontier) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + var result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); + line.styles = result.styles; + if (result.classes) line.styleClasses = result.classes; + else if (line.styleClasses) line.styleClasses = null; + if (updateFrontier === cm.doc.frontier) cm.doc.frontier++; + } + return line.styles; + } + + // Lightweight form of highlight -- proceed over this line and + // update state, but don't save a style array. Used for lines that + // aren't currently visible. + function processLine(cm, text, state, startAt) { + var mode = cm.doc.mode; + var stream = new StringStream(text, cm.options.tabSize); + stream.start = stream.pos = startAt || 0; + if (text == "") callBlankLine(mode, state); + while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { + readToken(mode, stream, state); + stream.start = stream.pos; + } + } + + // Convert a style as returned by a mode (either null, or a string + // containing one or more styles) to a CSS style. This is cached, + // and also looks for line-wide styles. + var styleToClassCache = {}, styleToClassCacheWithMode = {}; + function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) return null; + var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")); + } + + // Render the DOM representation of the text of a line. Also builds + // up a 'line map', which points at the DOM nodes that represent + // specific stretches of text, and is used by the measuring code. + // The returned object contains the DOM node, this map, and + // information about line-wide styles that were set by the mode. + function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + var content = elt("span", null, null, webkit ? "padding-right: .1px" : null); + var builder = {pre: elt("pre", [content]), content: content, + col: 0, pos: 0, cm: cm, + splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")}; + lineView.measure = {}; + + // Iterate over the logical lines that make up this visual line. + for (var i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + var line = i ? lineView.rest[i - 1] : lineView.line, order; + builder.pos = 0; + builder.addToken = buildToken; + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) + builder.addToken = buildTokenBadBidi(builder.addToken, order); + builder.map = []; + var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); + insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); + if (line.styleClasses) { + if (line.styleClasses.bgClass) + builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); + if (line.styleClasses.textClass) + builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))); + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map; + lineView.measure.cache = {}; + } else { + (lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map); + (lineView.measure.caches || (lineView.measure.caches = [])).push({}); + } + } + + // See issue #2901 + if (webkit && /\bcm-tab\b/.test(builder.content.lastChild.className)) + builder.content.className = "cm-tab-wrap-hack"; + + signal(cm, "renderLine", cm, lineView.line, builder.pre); + if (builder.pre.className) + builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); + + return builder; + } + + function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + ch.charCodeAt(0).toString(16); + token.setAttribute("aria-label", token.title); + return token; + } + + // Build up the DOM representation for a single token, and add it to + // the line map. Takes care to render special characters separately. + function buildToken(builder, text, style, startStyle, endStyle, title, css) { + if (!text) return; + var displayText = builder.splitSpaces ? text.replace(/ {3,}/g, splitSpaces) : text; + var special = builder.cm.state.specialChars, mustWrap = false; + if (!special.test(text)) { + builder.col += text.length; + var content = document.createTextNode(displayText); + builder.map.push(builder.pos, builder.pos + text.length, content); + if (ie && ie_version < 9) mustWrap = true; + builder.pos += text.length; + } else { + var content = document.createDocumentFragment(), pos = 0; + while (true) { + special.lastIndex = pos; + var m = special.exec(text); + var skipped = m ? m.index - pos : text.length - pos; + if (skipped) { + var txt = document.createTextNode(displayText.slice(pos, pos + skipped)); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.map.push(builder.pos, builder.pos + skipped, txt); + builder.col += skipped; + builder.pos += skipped; + } + if (!m) break; + pos += skipped + 1; + if (m[0] == "\t") { + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; + var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + txt.setAttribute("role", "presentation"); + txt.setAttribute("cm-text", "\t"); + builder.col += tabWidth; + } else { + var txt = builder.cm.options.specialCharPlaceholder(m[0]); + txt.setAttribute("cm-text", m[0]); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); + else content.appendChild(txt); + builder.col += 1; + } + builder.map.push(builder.pos, builder.pos + 1, txt); + builder.pos++; + } + } + if (style || startStyle || endStyle || mustWrap || css) { + var fullStyle = style || ""; + if (startStyle) fullStyle += startStyle; + if (endStyle) fullStyle += endStyle; + var token = elt("span", [content], fullStyle, css); + if (title) token.title = title; + return builder.content.appendChild(token); + } + builder.content.appendChild(content); + } + + function splitSpaces(old) { + var out = " "; + for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; + out += " "; + return out; + } + + // Work around nonsense dimensions being reported for stretches of + // right-to-left text. + function buildTokenBadBidi(inner, order) { + return function(builder, text, style, startStyle, endStyle, title, css) { + style = style ? style + " cm-force-border" : "cm-force-border"; + var start = builder.pos, end = start + text.length; + for (;;) { + // Find the part that overlaps with the start of this text + for (var i = 0; i < order.length; i++) { + var part = order[i]; + if (part.to > start && part.from <= start) break; + } + if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title, css); + inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css); + startStyle = null; + text = text.slice(part.to - start); + start = part.to; + } + }; + } + + function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + var widget = !ignoreWidget && marker.widgetNode; + if (widget) builder.map.push(builder.pos, builder.pos + size, widget); + if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { + if (!widget) + widget = builder.content.appendChild(document.createElement("span")); + widget.setAttribute("cm-marker", marker.id); + } + if (widget) { + builder.cm.display.input.setUneditable(widget); + builder.content.appendChild(widget); + } + builder.pos += size; + } + + // Outputs a number of spans to make up a line, taking highlighting + // and marked text into account. + function insertLineContent(line, builder, styles) { + var spans = line.markedSpans, allText = line.text, at = 0; + if (!spans) { + for (var i = 1; i < styles.length; i+=2) + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options)); + return; + } + + var len = allText.length, pos = 0, i = 1, text = "", style, css; + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = title = css = ""; + collapsed = null; nextChange = Infinity; + var foundBookmarks = []; + for (var j = 0; j < spans.length; ++j) { + var sp = spans[j], m = sp.marker; + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { + foundBookmarks.push(m); + } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { + if (sp.to != null && sp.to != pos && nextChange > sp.to) { + nextChange = sp.to; + spanEndStyle = ""; + } + if (m.className) spanStyle += " " + m.className; + if (m.css) css = m.css; + if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; + if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; + if (m.title && !title) title = m.title; + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + collapsed = sp; + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from; + } + } + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null); + if (collapsed.to == null) return; + if (collapsed.to == pos) collapsed = false; + } + if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j) + buildCollapsedSpan(builder, 0, foundBookmarks[j]); + } + if (pos >= len) break; + + var upto = Math.min(len, nextChange); + while (true) { + if (text) { + var end = pos + text.length; + if (!collapsed) { + var tokenText = end > upto ? text.slice(0, upto - pos) : text; + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css); + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} + pos = end; + spanStartStyle = ""; + } + text = allText.slice(at, at = styles[i++]); + style = interpretTokenStyle(styles[i++], builder.cm.options); + } + } + } + + // DOCUMENT DATA STRUCTURE + + // By default, updates that start and end at the beginning of a line + // are treated specially, in order to make the association of line + // widgets and marker elements with the text behave more intuitive. + function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore); + } + + // Perform a change on the document data structure. + function updateDoc(doc, change, markedSpans, estimateHeight) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null;} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight); + signalLater(line, "change", line, change); + } + function linesFor(start, end) { + for (var i = start, result = []; i < end; ++i) + result.push(new Line(text[i], spansFor(i), estimateHeight)); + return result; + } + + var from = change.from, to = change.to, text = change.text; + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; + + // Adjust the line structure + if (change.full) { + doc.insert(0, linesFor(0, text.length)); + doc.remove(text.length, doc.size - text.length); + } else if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = linesFor(0, text.length - 1); + update(lastLine, lastLine.text, lastSpans); + if (nlines) doc.remove(from.line, nlines); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); + } else { + var added = linesFor(1, text.length - 1); + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + doc.insert(from.line + 1, added); + } + } else if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); + doc.remove(from.line + 1, nlines); + } else { + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); + update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); + var added = linesFor(1, text.length - 1); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1); + doc.insert(from.line + 1, added); + } + + signalLater(doc, "change", doc, change); + } + + // The document is represented as a BTree consisting of leaves, with + // chunk of lines in them, and branches, with up to ten leaves or + // other branch nodes below them. The top node is always a branch + // node, and is the document object itself (meaning it has + // additional methods and properties). + // + // All nodes have parent links. The tree is used both to go from + // line numbers to line objects, and to go from objects to numbers. + // It also indexes by height, and is used to convert between height + // and line object, and to find the total height of the document. + // + // See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + + function LeafChunk(lines) { + this.lines = lines; + this.parent = null; + for (var i = 0, height = 0; i < lines.length; ++i) { + lines[i].parent = this; + height += lines[i].height; + } + this.height = height; + } + + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length; }, + // Remove the n lines at offset 'at'. + removeInner: function(at, n) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + cleanUpLine(line); + signalLater(line, "delete"); + } + this.lines.splice(at, n); + }, + // Helper used to collapse a small branch into a single leaf. + collapse: function(lines) { + lines.push.apply(lines, this.lines); + }, + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner: function(at, lines, height) { + this.height += height; + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + for (var i = 0; i < lines.length; ++i) lines[i].parent = this; + }, + // Used to iterate over a part of the tree. + iterN: function(at, n, op) { + for (var e = at + n; at < e; ++at) + if (op(this.lines[at])) return true; + } + }; + + function BranchChunk(children) { + this.children = children; + var size = 0, height = 0; + for (var i = 0; i < children.length; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this; + } + this.size = size; + this.height = height; + this.parent = null; + } + + BranchChunk.prototype = { + chunkSize: function() { return this.size; }, + removeInner: function(at, n) { + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.removeInner(at, rm); + this.height -= oldHeight - child.height; + if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) break; + at = 0; + } else at -= sz; + } + // If the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + collapse: function(lines) { + for (var i = 0; i < this.children.length; ++i) this.children[i].collapse(lines); + }, + insertInner: function(at, lines, height) { + this.size += lines.length; + this.height += height; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertInner(at, lines, height); + if (child.lines && child.lines.length > 50) { + while (child.lines.length > 50) { + var spilled = child.lines.splice(child.lines.length - 25, 25); + var newleaf = new LeafChunk(spilled); + child.height -= newleaf.height; + this.children.splice(i + 1, 0, newleaf); + newleaf.parent = this; + } + this.maybeSpill(); + } + break; + } + at -= sz; + } + }, + // When a node has grown, check whether it should be split. + maybeSpill: function() { + if (this.children.length <= 10) return; + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10); + me.parent.maybeSpill(); + }, + iterN: function(at, n, op) { + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) return true; + if ((n -= used) == 0) break; + at = 0; + } else at -= sz; + } + } + }; + + var nextDocId = 0; + var Doc = CodeMirror.Doc = function(text, mode, firstLine) { + if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); + if (firstLine == null) firstLine = 0; + + BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); + this.first = firstLine; + this.scrollTop = this.scrollLeft = 0; + this.cantEdit = false; + this.cleanGeneration = 1; + this.frontier = firstLine; + var start = Pos(firstLine, 0); + this.sel = simpleSelection(start); + this.history = new History(null); + this.id = ++nextDocId; + this.modeOption = mode; + + if (typeof text == "string") text = splitLines(text); + updateDoc(this, {from: start, to: start, text: text}); + setSelection(this, simpleSelection(start), sel_dontScroll); + }; + + Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + iter: function(from, to, op) { + if (op) this.iterN(from - this.first, to - from, op); + else this.iterN(this.first, this.first + this.size, from); + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + var height = 0; + for (var i = 0; i < lines.length; ++i) height += lines[i].height; + this.insertInner(at - this.first, lines, height); + }, + remove: function(at, n) { this.removeInner(at - this.first, n); }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + var lines = getLines(this, this.first, this.first + this.size); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + setValue: docMethodOp(function(code) { + var top = Pos(this.first, 0), last = this.first + this.size - 1; + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: splitLines(code), origin: "setValue", full: true}, true); + setSelection(this, simpleSelection(top)); + }), + replaceRange: function(code, from, to, origin) { + from = clipPos(this, from); + to = to ? clipPos(this, to) : from; + replaceRange(this, code, from, to, origin); + }, + getRange: function(from, to, lineSep) { + var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); + if (lineSep === false) return lines; + return lines.join(lineSep || "\n"); + }, + + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, + + getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, + getLineNumber: function(line) {return lineNo(line);}, + + getLineHandleVisualStart: function(line) { + if (typeof line == "number") line = getLine(this, line); + return visualLine(line); + }, + + lineCount: function() {return this.size;}, + firstLine: function() {return this.first;}, + lastLine: function() {return this.first + this.size - 1;}, + + clipPos: function(pos) {return clipPos(this, pos);}, + + getCursor: function(start) { + var range = this.sel.primary(), pos; + if (start == null || start == "head") pos = range.head; + else if (start == "anchor") pos = range.anchor; + else if (start == "end" || start == "to" || start === false) pos = range.to(); + else pos = range.from(); + return pos; + }, + listSelections: function() { return this.sel.ranges; }, + somethingSelected: function() {return this.sel.somethingSelected();}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options); + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options); + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options); + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads, options)); + }), + extendSelectionsBy: docMethodOp(function(f, options) { + extendSelections(this, map(this.sel.ranges, f), options); + }), + setSelections: docMethodOp(function(ranges, primary, options) { + if (!ranges.length) return; + for (var i = 0, out = []; i < ranges.length; i++) + out[i] = new Range(clipPos(this, ranges[i].anchor), + clipPos(this, ranges[i].head)); + if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex); + setSelection(this, normalizeSelection(out, primary), options); + }), + addSelection: docMethodOp(function(anchor, head, options) { + var ranges = this.sel.ranges.slice(0); + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))); + setSelection(this, normalizeSelection(ranges, ranges.length - 1), options); + }), + + getSelection: function(lineSep) { + var ranges = this.sel.ranges, lines; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + lines = lines ? lines.concat(sel) : sel; + } + if (lineSep === false) return lines; + else return lines.join(lineSep || "\n"); + }, + getSelections: function(lineSep) { + var parts = [], ranges = this.sel.ranges; + for (var i = 0; i < ranges.length; i++) { + var sel = getBetween(this, ranges[i].from(), ranges[i].to()); + if (lineSep !== false) sel = sel.join(lineSep || "\n"); + parts[i] = sel; + } + return parts; + }, + replaceSelection: function(code, collapse, origin) { + var dup = []; + for (var i = 0; i < this.sel.ranges.length; i++) + dup[i] = code; + this.replaceSelections(dup, collapse, origin || "+input"); + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + var changes = [], sel = this.sel; + for (var i = 0; i < sel.ranges.length; i++) { + var range = sel.ranges[i]; + changes[i] = {from: range.from(), to: range.to(), text: splitLines(code[i]), origin: origin}; + } + var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse); + for (var i = changes.length - 1; i >= 0; i--) + makeChange(this, changes[i]); + if (newSel) setSelectionReplaceHistory(this, newSel); + else if (this.cm) ensureCursorVisible(this.cm); + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true);}), + + setExtending: function(val) {this.extend = val;}, + getExtending: function() {return this.extend;}, + + historySize: function() { + var hist = this.history, done = 0, undone = 0; + for (var i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done; + for (var i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone; + return {undo: done, redo: undone}; + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration);}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true); + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; + return this.history.generation; + }, + isClean: function (gen) { + return this.history.generation == (gen || this.cleanGeneration); + }, + + getHistory: function() { + return {done: copyHistoryArray(this.history.done), + undone: copyHistoryArray(this.history.undone)}; + }, + setHistory: function(histData) { + var hist = this.history = new History(this.history.maxGeneration); + hist.done = copyHistoryArray(histData.done.slice(0), null, true); + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + if (!line[prop]) line[prop] = cls; + else if (classTest(cls).test(line[prop])) return false; + else line[prop] += " " + cls; + return true; + }); + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + var cur = line[prop]; + if (!cur) return false; + else if (cls == null) line[prop] = null; + else { + var found = cur.match(classTest(cls)); + if (!found) return false; + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; + } + return true; + }); + }), + + addLineWidget: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options); + }), + removeLineWidget: function(widget) { widget.clear(); }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, "range"); + }, + setBookmark: function(pos, options) { + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents}; + pos = clipPos(this, pos); + return markText(this, pos, pos, realOpts, "bookmark"); + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos); + var markers = [], spans = getLine(this, pos.line).markedSpans; + if (spans) for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if ((span.from == null || span.from <= pos.ch) && + (span.to == null || span.to >= pos.ch)) + markers.push(span.marker.parent || span.marker); + } + return markers; + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to); + var found = [], lineNo = from.line; + this.iter(from.line, to.line + 1, function(line) { + var spans = line.markedSpans; + if (spans) for (var i = 0; i < spans.length; i++) { + var span = spans[i]; + if (!(lineNo == from.line && from.ch > span.to || + span.from == null && lineNo != from.line|| + lineNo == to.line && span.from > to.ch) && + (!filter || filter(span.marker))) + found.push(span.marker.parent || span.marker); + } + ++lineNo; + }); + return found; + }, + getAllMarks: function() { + var markers = []; + this.iter(function(line) { + var sps = line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) + if (sps[i].from != null) markers.push(sps[i].marker); + }); + return markers; + }, + + posFromIndex: function(off) { + var ch, lineNo = this.first; + this.iter(function(line) { + var sz = line.text.length + 1; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos(this, Pos(lineNo, ch)); + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords); + var index = coords.ch; + if (coords.line < this.first || coords.ch < 0) return 0; + this.iter(this.first, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + + copy: function(copyHistory) { + var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first); + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; + doc.sel = this.sel; + doc.extend = false; + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth; + doc.setHistory(this.getHistory()); + } + return doc; + }, + + linkedDoc: function(options) { + if (!options) options = {}; + var from = this.first, to = this.first + this.size; + if (options.from != null && options.from > from) from = options.from; + if (options.to != null && options.to < to) to = options.to; + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from); + if (options.sharedHist) copy.history = this.history; + (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; + copySharedMarkers(copy, findSharedMarkers(this)); + return copy; + }, + unlinkDoc: function(other) { + if (other instanceof CodeMirror) other = other.doc; + if (this.linked) for (var i = 0; i < this.linked.length; ++i) { + var link = this.linked[i]; + if (link.doc != other) continue; + this.linked.splice(i, 1); + other.unlinkDoc(this); + detachSharedMarkers(findSharedMarkers(this)); + break; + } + // If the histories were shared, split them again + if (other.history == this.history) { + var splitIds = [other.id]; + linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); + other.history = new History(null); + other.history.done = copyHistoryArray(this.history.done, splitIds); + other.history.undone = copyHistoryArray(this.history.undone, splitIds); + } + }, + iterLinkedDocs: function(f) {linkedDocs(this, f);}, + + getMode: function() {return this.mode;}, + getEditor: function() {return this.cm;} + }); + + // Public alias. + Doc.prototype.eachLine = Doc.prototype.iter; + + // Set up methods on CodeMirror's prototype to redirect to the editor's document. + var dontDelegate = "iter insert remove copy getEditor".split(" "); + for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) + CodeMirror.prototype[prop] = (function(method) { + return function() {return method.apply(this.doc, arguments);}; + })(Doc.prototype[prop]); + + eventMixin(Doc); + + // Call f for all linked documents. + function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { + var rel = doc.linked[i]; + if (rel.doc == skip) continue; + var shared = sharedHist && rel.sharedHist; + if (sharedHistOnly && !shared) continue; + f(rel.doc, shared); + propagate(rel.doc, doc, shared); + } + } + propagate(doc, null, true); + } + + // Attach a document to an editor. + function attachDoc(cm, doc) { + if (doc.cm) throw new Error("This document is already in use."); + cm.doc = doc; + doc.cm = cm; + estimateLineHeights(cm); + loadMode(cm); + if (!cm.options.lineWrapping) findMaxLine(cm); + cm.options.mode = doc.modeOption; + regChange(cm); + } + + // LINE UTILITIES + + // Find the line object corresponding to the given line number. + function getLine(doc, n) { + n -= doc.first; + if (n < 0 || n >= doc.size) throw new Error("There is no line " + (n + doc.first) + " in the document."); + for (var chunk = doc; !chunk.lines;) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break; } + n -= sz; + } + } + return chunk.lines[n]; + } + + // Get the part of a document between two positions, as an array of + // strings. + function getBetween(doc, start, end) { + var out = [], n = start.line; + doc.iter(start.line, end.line + 1, function(line) { + var text = line.text; + if (n == end.line) text = text.slice(0, end.ch); + if (n == start.line) text = text.slice(start.ch); + out.push(text); + ++n; + }); + return out; + } + // Get the lines between from and to, as array of strings. + function getLines(doc, from, to) { + var out = []; + doc.iter(from, to, function(line) { out.push(line.text); }); + return out; + } + + // Update the height of a line, propagating the height change + // upwards to parent nodes. + function updateLineHeight(line, height) { + var diff = height - line.height; + if (diff) for (var n = line; n; n = n.parent) n.height += diff; + } + + // Given a line object, find its line number by walking up through + // its parent links. + function lineNo(line) { + if (line.parent == null) return null; + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0;; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no + cur.first; + } + + // Find the line at the given vertical position, using the height + // information in the document tree. + function lineAtHeight(chunk, h) { + var n = chunk.first; + outer: do { + for (var i = 0; i < chunk.children.length; ++i) { + var child = chunk.children[i], ch = child.height; + if (h < ch) { chunk = child; continue outer; } + h -= ch; + n += child.chunkSize(); + } + return n; + } while (!chunk.lines); + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) break; + h -= lh; + } + return n + i; + } + + + // Find the height above the given line. + function heightAtLine(lineObj) { + lineObj = visualLine(lineObj); + + var h = 0, chunk = lineObj.parent; + for (var i = 0; i < chunk.lines.length; ++i) { + var line = chunk.lines[i]; + if (line == lineObj) break; + else h += line.height; + } + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (var i = 0; i < p.children.length; ++i) { + var cur = p.children[i]; + if (cur == chunk) break; + else h += cur.height; + } + } + return h; + } + + // Get the bidi ordering for the given line (and cache it). Returns + // false for lines that are fully left-to-right, and an array of + // BidiSpan objects otherwise. + function getOrder(line) { + var order = line.order; + if (order == null) order = line.order = bidiOrdering(line.text); + return order; + } + + // HISTORY + + function History(startGen) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = []; + this.undoDepth = Infinity; + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0; + this.lastOp = this.lastSelOp = null; + this.lastOrigin = this.lastSelOrigin = null; + // Used by the isClean() method + this.generation = this.maxGeneration = startGen || 1; + } + + // Create a history change event from an updateDoc-style change + // object. + function historyChangeFromChange(doc, change) { + var histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); + linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); + return histChange; + } + + // Pop all selection events off the end of a history array. Stop at + // a change event. + function clearSelectionEvents(array) { + while (array.length) { + var last = lst(array); + if (last.ranges) array.pop(); + else break; + } + } + + // Find the top change event in the history. Pop off selection + // events that are in the way. + function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done); + return lst(hist.done); + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done); + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop(); + return lst(hist.done); + } + } + + // Register a change in the history. Merges changes that are within + // a single operation, ore are close together with an origin that + // allows merging (starting with "+") into a single event. + function addChangeToHistory(doc, change, selAfter, opId) { + var hist = doc.history; + hist.undone.length = 0; + var time = +new Date, cur; + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + var last = lst(cur.changes); + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // Optimized case for simple insertion -- don't want to add + // new changesets for every character typed + last.to = changeEnd(change); + } else { + // Add new sub-event + cur.changes.push(historyChangeFromChange(doc, change)); + } + } else { + // Can not be merged, start a new event. + var before = lst(hist.done); + if (!before || !before.ranges) + pushSelectionToHistory(doc.sel, hist.done); + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation}; + hist.done.push(cur); + while (hist.done.length > hist.undoDepth) { + hist.done.shift(); + if (!hist.done[0].ranges) hist.done.shift(); + } + } + hist.done.push(selAfter); + hist.generation = ++hist.maxGeneration; + hist.lastModTime = hist.lastSelTime = time; + hist.lastOp = hist.lastSelOp = opId; + hist.lastOrigin = hist.lastSelOrigin = change.origin; + + if (!last) signal(doc, "historyAdded"); + } + + function selectionEventCanBeMerged(doc, origin, prev, sel) { + var ch = origin.charAt(0); + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500); + } + + // Called whenever the selection changes, sets the new selection as + // the pending selection in the history, and pushes the old pending + // selection into the 'done' array when it was significantly + // different (in number of selected ranges, emptiness, or time). + function addSelectionToHistory(doc, sel, opId, options) { + var hist = doc.history, origin = options && options.origin; + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastSelOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + hist.done[hist.done.length - 1] = sel; + else + pushSelectionToHistory(sel, hist.done); + + hist.lastSelTime = +new Date; + hist.lastSelOrigin = origin; + hist.lastSelOp = opId; + if (options && options.clearRedo !== false) + clearSelectionEvents(hist.undone); + } + + function pushSelectionToHistory(sel, dest) { + var top = lst(dest); + if (!(top && top.ranges && top.equals(sel))) + dest.push(sel); + } + + // Used to store marked span information in the history. + function attachLocalSpans(doc, change, from, to) { + var existing = change["spans_" + doc.id], n = 0; + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) { + if (line.markedSpans) + (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; + ++n; + }); + } + + // When un/re-doing restores text containing marked spans, those + // that have been explicitly cleared should not be restored. + function removeClearedSpans(spans) { + if (!spans) return null; + for (var i = 0, out; i < spans.length; ++i) { + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } + else if (out) out.push(spans[i]); + } + return !out ? spans : out.length ? out : null; + } + + // Retrieve and filter the old marked spans stored in a change event. + function getOldSpans(doc, change) { + var found = change["spans_" + doc.id]; + if (!found) return null; + for (var i = 0, nw = []; i < change.text.length; ++i) + nw.push(removeClearedSpans(found[i])); + return nw; + } + + // Used both to provide a JSON-safe object in .getHistory, and, when + // detaching a document, to split the history in two + function copyHistoryArray(events, newGroup, instantiateSel) { + for (var i = 0, copy = []; i < events.length; ++i) { + var event = events[i]; + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event); + continue; + } + var changes = event.changes, newChanges = []; + copy.push({changes: newChanges}); + for (var j = 0; j < changes.length; ++j) { + var change = changes[j], m; + newChanges.push({from: change.from, to: change.to, text: change.text}); + if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { + if (indexOf(newGroup, Number(m[1])) > -1) { + lst(newChanges)[prop] = change[prop]; + delete change[prop]; + } + } + } + } + return copy; + } + + // Rebasing/resetting history to deal with externally-sourced changes + + function rebaseHistSelSingle(pos, from, to, diff) { + if (to < pos.line) { + pos.line += diff; + } else if (from < pos.line) { + pos.line = from; + pos.ch = 0; + } + } + + // Tries to rebase an array of history events given a change in the + // document. If the change touches the same lines as the event, the + // event, and everything 'behind' it, is discarded. If the change is + // before the event, the event's positions are updated. Uses a + // copy-on-write scheme for the positions, to avoid having to + // reallocate them all on every rebase, but also avoid problems with + // shared position objects being unsafely updated. + function rebaseHistArray(array, from, to, diff) { + for (var i = 0; i < array.length; ++i) { + var sub = array[i], ok = true; + if (sub.ranges) { + if (!sub.copied) { sub = array[i] = sub.deepCopy(); sub.copied = true; } + for (var j = 0; j < sub.ranges.length; j++) { + rebaseHistSelSingle(sub.ranges[j].anchor, from, to, diff); + rebaseHistSelSingle(sub.ranges[j].head, from, to, diff); + } + continue; + } + for (var j = 0; j < sub.changes.length; ++j) { + var cur = sub.changes[j]; + if (to < cur.from.line) { + cur.from = Pos(cur.from.line + diff, cur.from.ch); + cur.to = Pos(cur.to.line + diff, cur.to.ch); + } else if (from <= cur.to.line) { + ok = false; + break; + } + } + if (!ok) { + array.splice(0, i + 1); + i = 0; + } + } + } + + function rebaseHist(hist, change) { + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; + rebaseHistArray(hist.done, from, to, diff); + rebaseHistArray(hist.undone, from, to, diff); + } + + // EVENT UTILITIES + + // Due to the fact that we still support jurassic IE versions, some + // compatibility wrappers are needed. + + var e_preventDefault = CodeMirror.e_preventDefault = function(e) { + if (e.preventDefault) e.preventDefault(); + else e.returnValue = false; + }; + var e_stopPropagation = CodeMirror.e_stopPropagation = function(e) { + if (e.stopPropagation) e.stopPropagation(); + else e.cancelBubble = true; + }; + function e_defaultPrevented(e) { + return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; + } + var e_stop = CodeMirror.e_stop = function(e) {e_preventDefault(e); e_stopPropagation(e);}; + + function e_target(e) {return e.target || e.srcElement;} + function e_button(e) { + var b = e.which; + if (b == null) { + if (e.button & 1) b = 1; + else if (e.button & 2) b = 3; + else if (e.button & 4) b = 2; + } + if (mac && e.ctrlKey && b == 1) b = 3; + return b; + } + + // EVENT HANDLING + + // Lightweight event framework. on/off also work on DOM nodes, + // registering native DOM handlers. + + var on = CodeMirror.on = function(emitter, type, f) { + if (emitter.addEventListener) + emitter.addEventListener(type, f, false); + else if (emitter.attachEvent) + emitter.attachEvent("on" + type, f); + else { + var map = emitter._handlers || (emitter._handlers = {}); + var arr = map[type] || (map[type] = []); + arr.push(f); + } + }; + + var off = CodeMirror.off = function(emitter, type, f) { + if (emitter.removeEventListener) + emitter.removeEventListener(type, f, false); + else if (emitter.detachEvent) + emitter.detachEvent("on" + type, f); + else { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + for (var i = 0; i < arr.length; ++i) + if (arr[i] == f) { arr.splice(i, 1); break; } + } + }; + + var signal = CodeMirror.signal = function(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2); + for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); + }; + + var orphanDelayedCallbacks = null; + + // Often, we want to signal events at a point where we are in the + // middle of some work, but don't want the handler to start calling + // other methods on the editor, which might be in an inconsistent + // state or simply not expect any other events to happen. + // signalLater looks whether there are any handlers, and schedules + // them to be executed when the last operation ends, or, if no + // operation is active, when a timeout fires. + function signalLater(emitter, type /*, values...*/) { + var arr = emitter._handlers && emitter._handlers[type]; + if (!arr) return; + var args = Array.prototype.slice.call(arguments, 2), list; + if (operationGroup) { + list = operationGroup.delayedCallbacks; + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks; + } else { + list = orphanDelayedCallbacks = []; + setTimeout(fireOrphanDelayed, 0); + } + function bnd(f) {return function(){f.apply(null, args);};}; + for (var i = 0; i < arr.length; ++i) + list.push(bnd(arr[i])); + } + + function fireOrphanDelayed() { + var delayed = orphanDelayedCallbacks; + orphanDelayedCallbacks = null; + for (var i = 0; i < delayed.length; ++i) delayed[i](); + } + + // The DOM events that CodeMirror handles can be overridden by + // registering a (non-DOM) handler on the editor for the event name, + // and preventDefault-ing the event in that handler. + function signalDOMEvent(cm, e, override) { + if (typeof e == "string") + e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; + signal(cm, override || e.type, cm, e); + return e_defaultPrevented(e) || e.codemirrorIgnore; + } + + function signalCursorActivity(cm) { + var arr = cm._handlers && cm._handlers.cursorActivity; + if (!arr) return; + var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); + for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) + set.push(arr[i]); + } + + function hasHandler(emitter, type) { + var arr = emitter._handlers && emitter._handlers[type]; + return arr && arr.length > 0; + } + + // Add on and off methods to a constructor's prototype, to make + // registering events on such objects more convenient. + function eventMixin(ctor) { + ctor.prototype.on = function(type, f) {on(this, type, f);}; + ctor.prototype.off = function(type, f) {off(this, type, f);}; + } + + // MISC UTILITIES + + // Number of pixels added to scroller and sizer to hide scrollbar + var scrollerGap = 30; + + // Returned or thrown by various protocols to signal 'I'm not + // handling this'. + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; + + // Reused option objects for setSelection & friends + var sel_dontScroll = {scroll: false}, sel_mouse = {origin: "*mouse"}, sel_move = {origin: "+move"}; + + function Delayed() {this.id = null;} + Delayed.prototype.set = function(ms, f) { + clearTimeout(this.id); + this.id = setTimeout(f, ms); + }; + + // Counts the column offset in a string, taking tabs into account. + // Used mostly to find indentation. + var countColumn = CodeMirror.countColumn = function(string, end, tabSize, startIndex, startValue) { + if (end == null) { + end = string.search(/[^\s\u00a0]/); + if (end == -1) end = string.length; + } + for (var i = startIndex || 0, n = startValue || 0;;) { + var nextTab = string.indexOf("\t", i); + if (nextTab < 0 || nextTab >= end) + return n + (end - i); + n += nextTab - i; + n += tabSize - (n % tabSize); + i = nextTab + 1; + } + }; + + // The inverse of countColumn -- find the offset that corresponds to + // a particular column. + function findColumn(string, goal, tabSize) { + for (var pos = 0, col = 0;;) { + var nextTab = string.indexOf("\t", pos); + if (nextTab == -1) nextTab = string.length; + var skipped = nextTab - pos; + if (nextTab == string.length || col + skipped >= goal) + return pos + Math.min(skipped, goal - col); + col += nextTab - pos; + col += tabSize - (col % tabSize); + pos = nextTab + 1; + if (col >= goal) return pos; + } + } + + var spaceStrs = [""]; + function spaceStr(n) { + while (spaceStrs.length <= n) + spaceStrs.push(lst(spaceStrs) + " "); + return spaceStrs[n]; + } + + function lst(arr) { return arr[arr.length-1]; } + + var selectInput = function(node) { node.select(); }; + if (ios) // Mobile Safari apparently has a bug where select() is broken. + selectInput = function(node) { node.selectionStart = 0; node.selectionEnd = node.value.length; }; + else if (ie) // Suppress mysterious IE10 errors + selectInput = function(node) { try { node.select(); } catch(_e) {} }; + + function indexOf(array, elt) { + for (var i = 0; i < array.length; ++i) + if (array[i] == elt) return i; + return -1; + } + function map(array, f) { + var out = []; + for (var i = 0; i < array.length; i++) out[i] = f(array[i], i); + return out; + } + + function nothing() {} + + function createObj(base, props) { + var inst; + if (Object.create) { + inst = Object.create(base); + } else { + nothing.prototype = base; + inst = new nothing(); + } + if (props) copyObj(props, inst); + return inst; + }; + + function copyObj(obj, target, overwrite) { + if (!target) target = {}; + for (var prop in obj) + if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + target[prop] = obj[prop]; + return target; + } + + function bind(f) { + var args = Array.prototype.slice.call(arguments, 1); + return function(){return f.apply(null, args);}; + } + + var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + var isWordCharBasic = CodeMirror.isWordChar = function(ch) { + return /\w/.test(ch) || ch > "\x80" && + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); + }; + function isWordChar(ch, helper) { + if (!helper) return isWordCharBasic(ch); + if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true; + return helper.test(ch); + } + + function isEmpty(obj) { + for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; + return true; + } + + // Extending unicode characters. A series of a non-extending char + + // any number of extending chars is treated as a single unit as far + // as editing and measuring is concerned. This is not fully correct, + // since some scripts/fonts/browsers also treat other configurations + // of code points as a group. + var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; + function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); } + + // DOM UTILITIES + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) e.className = className; + if (style) e.style.cssText = style; + if (typeof content == "string") e.appendChild(document.createTextNode(content)); + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); + return e; + } + + var range; + if (document.createRange) range = function(node, start, end, endNode) { + var r = document.createRange(); + r.setEnd(endNode || node, end); + r.setStart(node, start); + return r; + }; + else range = function(node, start, end) { + var r = document.body.createTextRange(); + try { r.moveToElementText(node.parentNode); } + catch(e) { return r; } + r.collapse(true); + r.moveEnd("character", end); + r.moveStart("character", start); + return r; + }; + + function removeChildren(e) { + for (var count = e.childNodes.length; count > 0; --count) + e.removeChild(e.firstChild); + return e; + } + + function removeChildrenAndAdd(parent, e) { + return removeChildren(parent).appendChild(e); + } + + var contains = CodeMirror.contains = function(parent, child) { + if (child.nodeType == 3) // Android browser always returns false when child is a textnode + child = child.parentNode; + if (parent.contains) + return parent.contains(child); + do { + if (child.nodeType == 11) child = child.host; + if (child == parent) return true; + } while (child = child.parentNode); + }; + + function activeElt() { return document.activeElement; } + // Older versions of IE throws unspecified error when touching + // document.activeElement in some cases (during loading, in iframe) + if (ie && ie_version < 11) activeElt = function() { + try { return document.activeElement; } + catch(e) { return document.body; } + }; + + function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*"); } + var rmClass = CodeMirror.rmClass = function(node, cls) { + var current = node.className; + var match = classTest(cls).exec(current); + if (match) { + var after = current.slice(match.index + match[0].length); + node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); + } + }; + var addClass = CodeMirror.addClass = function(node, cls) { + var current = node.className; + if (!classTest(cls).test(current)) node.className += (current ? " " : "") + cls; + }; + function joinClasses(a, b) { + var as = a.split(" "); + for (var i = 0; i < as.length; i++) + if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i]; + return b; + } + + // WINDOW-WIDE EVENTS + + // These must be handled carefully, because naively registering a + // handler for each editor will cause the editors to never be + // garbage collected. + + function forEachCodeMirror(f) { + if (!document.body.getElementsByClassName) return; + var byClass = document.body.getElementsByClassName("CodeMirror"); + for (var i = 0; i < byClass.length; i++) { + var cm = byClass[i].CodeMirror; + if (cm) f(cm); + } + } + + var globalsRegistered = false; + function ensureGlobalHandlers() { + if (globalsRegistered) return; + registerGlobalHandlers(); + globalsRegistered = true; + } + function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + var resizeTimer; + on(window, "resize", function() { + if (resizeTimer == null) resizeTimer = setTimeout(function() { + resizeTimer = null; + forEachCodeMirror(onResize); + }, 100); + }); + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", function() { + forEachCodeMirror(onBlur); + }); + } + + // FEATURE DETECTION + + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie && ie_version < 9) return false; + var div = elt('div'); + return "draggable" in div || "dragDrop" in div; + }(); + + var zwspSupported; + function zeroWidthElement(measure) { + if (zwspSupported == null) { + var test = elt("span", "\u200b"); + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); + if (measure.firstChild.offsetHeight != 0) + zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); + } + var node = zwspSupported ? elt("span", "\u200b") : + elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); + node.setAttribute("cm-text", ""); + return node; + } + + // Feature-detect IE's crummy client rect reporting for bidi text + var badBidiRects; + function hasBadBidiRects(measure) { + if (badBidiRects != null) return badBidiRects; + var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); + var r0 = range(txt, 0, 1).getBoundingClientRect(); + if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780) + var r1 = range(txt, 1, 2).getBoundingClientRect(); + return badBidiRects = (r1.right - r0.right < 3); + } + + // See if "".split is the broken IE version, if so, provide an + // alternative way to split lines. + var splitLines = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { + var pos = 0, result = [], l = string.length; + while (pos <= l) { + var nl = string.indexOf("\n", pos); + if (nl == -1) nl = string.length; + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); + var rt = line.indexOf("\r"); + if (rt != -1) { + result.push(line.slice(0, rt)); + pos += rt + 1; + } else { + result.push(line); + pos = nl + 1; + } + } + return result; + } : function(string){return string.split(/\r\n?|\n/);}; + + var hasSelection = window.getSelection ? function(te) { + try { return te.selectionStart != te.selectionEnd; } + catch(e) { return false; } + } : function(te) { + try {var range = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range || range.parentElement() != te) return false; + return range.compareEndPoints("StartToEnd", range) != 0; + }; + + var hasCopyEvent = (function() { + var e = elt("div"); + if ("oncopy" in e) return true; + e.setAttribute("oncopy", "return;"); + return typeof e.oncopy == "function"; + })(); + + var badZoomedRects = null; + function hasBadZoomedRects(measure) { + if (badZoomedRects != null) return badZoomedRects; + var node = removeChildrenAndAdd(measure, elt("span", "x")); + var normal = node.getBoundingClientRect(); + var fromRange = range(node, 0, 1).getBoundingClientRect(); + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; + } + + // KEY NAMES + + var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"}; + CodeMirror.keyNames = keyNames; + (function() { + // Number keys + for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i); + // Alphabetic keys + for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); + // Function keys + for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; + })(); + + // BIDI HELPERS + + function iterateBidiSections(order, from, to, f) { + if (!order) return f(from, to, "ltr"); + var found = false; + for (var i = 0; i < order.length; ++i) { + var part = order[i]; + if (part.from < to && part.to > from || from == to && part.to == from) { + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); + found = true; + } + } + if (!found) f(from, to, "ltr"); + } + + function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } + function bidiRight(part) { return part.level % 2 ? part.from : part.to; } + + function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } + function lineRight(line) { + var order = getOrder(line); + if (!order) return line.text.length; + return bidiRight(lst(order)); + } + + function lineStart(cm, lineN) { + var line = getLine(cm.doc, lineN); + var visual = visualLine(line); + if (visual != line) lineN = lineNo(visual); + var order = getOrder(visual); + var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); + return Pos(lineN, ch); + } + function lineEnd(cm, lineN) { + var merged, line = getLine(cm.doc, lineN); + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line; + lineN = null; + } + var order = getOrder(line); + var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); + return Pos(lineN == null ? lineNo(line) : lineN, ch); + } + function lineStartSmart(cm, pos) { + var start = lineStart(cm, pos.line); + var line = getLine(cm.doc, start.line); + var order = getOrder(line); + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)); + var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; + return Pos(start.line, inWS ? 0 : firstNonWS); + } + return start; + } + + function compareBidiLevel(order, a, b) { + var linedir = order[0].level; + if (a == linedir) return true; + if (b == linedir) return false; + return a < b; + } + var bidiOther; + function getBidiPartAt(order, pos) { + bidiOther = null; + for (var i = 0, found; i < order.length; ++i) { + var cur = order[i]; + if (cur.from < pos && cur.to > pos) return i; + if ((cur.from == pos || cur.to == pos)) { + if (found == null) { + found = i; + } else if (compareBidiLevel(order, cur.level, order[found].level)) { + if (cur.from != cur.to) bidiOther = found; + return i; + } else { + if (cur.from != cur.to) bidiOther = i; + return found; + } + } + } + return found; + } + + function moveInLine(line, pos, dir, byUnit) { + if (!byUnit) return pos + dir; + do pos += dir; + while (pos > 0 && isExtendingChar(line.text.charAt(pos))); + return pos; + } + + // This is needed in order to move 'visually' through bi-directional + // text -- i.e., pressing left should make the cursor go left, even + // when in RTL text. The tricky part is the 'jumps', where RTL and + // LTR text touch each other. This often requires the cursor offset + // to move more than one unit, in order to visually move one unit. + function moveVisually(line, start, dir, byUnit) { + var bidi = getOrder(line); + if (!bidi) return moveLogically(line, start, dir, byUnit); + var pos = getBidiPartAt(bidi, start), part = bidi[pos]; + var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); + + for (;;) { + if (target > part.from && target < part.to) return target; + if (target == part.from || target == part.to) { + if (getBidiPartAt(bidi, target) == pos) return target; + part = bidi[pos += dir]; + return (dir > 0) == part.level % 2 ? part.to : part.from; + } else { + part = bidi[pos += dir]; + if (!part) return null; + if ((dir > 0) == part.level % 2) + target = moveInLine(line, part.to, -1, byUnit); + else + target = moveInLine(line, part.from, 1, byUnit); + } + } + } + + function moveLogically(line, start, dir, byUnit) { + var target = start + dir; + if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir; + return target < 0 || target > line.text.length ? null : target; + } + + // Bidirectional ordering algorithm + // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm + // that this (partially) implements. + + // One-char codes used for character types: + // L (L): Left-to-Right + // R (R): Right-to-Left + // r (AL): Right-to-Left Arabic + // 1 (EN): European Number + // + (ES): European Number Separator + // % (ET): European Number Terminator + // n (AN): Arabic Number + // , (CS): Common Number Separator + // m (NSM): Non-Spacing Mark + // b (BN): Boundary Neutral + // s (B): Paragraph Separator + // t (S): Segment Separator + // w (WS): Whitespace + // N (ON): Other Neutrals + + // Returns null if characters are ordered as they appear + // (left-to-right), or an array of sections ({from, to, level} + // objects) in the order in which they occur visually. + var bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN"; + // Character types for codepoints 0x600 to 0x6ff + var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm"; + function charType(code) { + if (code <= 0xf7) return lowTypes.charAt(code); + else if (0x590 <= code && code <= 0x5f4) return "R"; + else if (0x600 <= code && code <= 0x6ed) return arabicTypes.charAt(code - 0x600); + else if (0x6ee <= code && code <= 0x8ac) return "r"; + else if (0x2000 <= code && code <= 0x200b) return "w"; + else if (code == 0x200c) return "b"; + else return "L"; + } + + var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; + var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; + // Browsers seem to always treat the boundaries of block elements as being L. + var outerType = "L"; + + function BidiSpan(level, from, to) { + this.level = level; + this.from = from; this.to = to; + } + + return function(str) { + if (!bidiRE.test(str)) return false; + var len = str.length, types = []; + for (var i = 0, type; i < len; ++i) + types.push(type = charType(str.charCodeAt(i))); + + // W1. Examine each non-spacing mark (NSM) in the level run, and + // change the type of the NSM to the type of the previous + // character. If the NSM is at the start of the level run, it will + // get the type of sor. + for (var i = 0, prev = outerType; i < len; ++i) { + var type = types[i]; + if (type == "m") types[i] = prev; + else prev = type; + } + + // W2. Search backwards from each instance of a European number + // until the first strong type (R, L, AL, or sor) is found. If an + // AL is found, change the type of the European number to Arabic + // number. + // W3. Change all ALs to R. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (type == "1" && cur == "r") types[i] = "n"; + else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } + } + + // W4. A single European separator between two European numbers + // changes to a European number. A single common separator between + // two numbers of the same type changes to that type. + for (var i = 1, prev = types[0]; i < len - 1; ++i) { + var type = types[i]; + if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; + else if (type == "," && prev == types[i+1] && + (prev == "1" || prev == "n")) types[i] = prev; + prev = type; + } + + // W5. A sequence of European terminators adjacent to European + // numbers changes to all European numbers. + // W6. Otherwise, separators and terminators change to Other + // Neutral. + for (var i = 0; i < len; ++i) { + var type = types[i]; + if (type == ",") types[i] = "N"; + else if (type == "%") { + for (var end = i + 1; end < len && types[end] == "%"; ++end) {} + var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // W7. Search backwards from each instance of a European number + // until the first strong type (R, L, or sor) is found. If an L is + // found, then change the type of the European number to L. + for (var i = 0, cur = outerType; i < len; ++i) { + var type = types[i]; + if (cur == "L" && type == "1") types[i] = "L"; + else if (isStrong.test(type)) cur = type; + } + + // N1. A sequence of neutrals takes the direction of the + // surrounding strong text if the text on both sides has the same + // direction. European and Arabic numbers act as if they were R in + // terms of their influence on neutrals. Start-of-level-run (sor) + // and end-of-level-run (eor) are used at level run boundaries. + // N2. Any remaining neutrals take the embedding direction. + for (var i = 0; i < len; ++i) { + if (isNeutral.test(types[i])) { + for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} + var before = (i ? types[i-1] : outerType) == "L"; + var after = (end < len ? types[end] : outerType) == "L"; + var replace = before || after ? "L" : "R"; + for (var j = i; j < end; ++j) types[j] = replace; + i = end - 1; + } + } + + // Here we depart from the documented algorithm, in order to avoid + // building up an actual levels array. Since there are only three + // levels (0, 1, 2) in an implementation that doesn't take + // explicit embedding into account, we can build up the order on + // the fly, without following the level-based algorithm. + var order = [], m; + for (var i = 0; i < len;) { + if (countsAsLeft.test(types[i])) { + var start = i; + for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} + order.push(new BidiSpan(0, start, i)); + } else { + var pos = i, at = order.length; + for (++i; i < len && types[i] != "L"; ++i) {} + for (var j = pos; j < i;) { + if (countsAsNum.test(types[j])) { + if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j)); + var nstart = j; + for (++j; j < i && countsAsNum.test(types[j]); ++j) {} + order.splice(at, 0, new BidiSpan(2, nstart, j)); + pos = j; + } else ++j; + } + if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)); + } + } + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length; + order.unshift(new BidiSpan(0, 0, m[0].length)); + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length; + order.push(new BidiSpan(0, len - m[0].length, len)); + } + if (order[0].level == 2) + order.unshift(new BidiSpan(1, order[0].to, order[0].to)); + if (order[0].level != lst(order).level) + order.push(new BidiSpan(order[0].level, len, len)); + + return order; + }; + })(); + + // THE END + + CodeMirror.version = "5.3.1"; + + return CodeMirror; +}); diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/static/codemirror/mode/xml/xml.js basex-8.2.3/basex-api/src/main/webapp/dba/static/codemirror/mode/xml/xml.js --- basex-8.1.1/basex-api/src/main/webapp/dba/static/codemirror/mode/xml/xml.js 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/static/codemirror/mode/xml/xml.js 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,384 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("xml", function(config, parserConfig) { + var indentUnit = config.indentUnit; + var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1; + var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag; + if (multilineTagIndentPastTag == null) multilineTagIndentPastTag = true; + + var Kludges = parserConfig.htmlMode ? { + autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, + 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, + 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, + 'track': true, 'wbr': true, 'menuitem': true}, + implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, + 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, + 'th': true, 'tr': true}, + contextGrabbers: { + 'dd': {'dd': true, 'dt': true}, + 'dt': {'dd': true, 'dt': true}, + 'li': {'li': true}, + 'option': {'option': true, 'optgroup': true}, + 'optgroup': {'optgroup': true}, + 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true, + 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true, + 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, + 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true, + 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true}, + 'rp': {'rp': true, 'rt': true}, + 'rt': {'rp': true, 'rt': true}, + 'tbody': {'tbody': true, 'tfoot': true}, + 'td': {'td': true, 'th': true}, + 'tfoot': {'tbody': true}, + 'th': {'td': true, 'th': true}, + 'thead': {'tbody': true, 'tfoot': true}, + 'tr': {'tr': true} + }, + doNotIndent: {"pre": true}, + allowUnquoted: true, + allowMissing: true, + caseFold: true + } : { + autoSelfClosers: {}, + implicitlyClosed: {}, + contextGrabbers: {}, + doNotIndent: {}, + allowUnquoted: false, + allowMissing: false, + caseFold: false + }; + var alignCDATA = parserConfig.alignCDATA; + + // Return variables for tokenizers + var type, setStyle; + + function inText(stream, state) { + function chain(parser) { + state.tokenize = parser; + return parser(stream, state); + } + + var ch = stream.next(); + if (ch == "<") { + if (stream.eat("!")) { + if (stream.eat("[")) { + if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); + else return null; + } else if (stream.match("--")) { + return chain(inBlock("comment", "-->")); + } else if (stream.match("DOCTYPE", true, true)) { + stream.eatWhile(/[\w\._\-]/); + return chain(doctype(1)); + } else { + return null; + } + } else if (stream.eat("?")) { + stream.eatWhile(/[\w\._\-]/); + state.tokenize = inBlock("meta", "?>"); + return "meta"; + } else { + type = stream.eat("/") ? "closeTag" : "openTag"; + state.tokenize = inTag; + return "tag bracket"; + } + } else if (ch == "&") { + var ok; + if (stream.eat("#")) { + if (stream.eat("x")) { + ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); + } else { + ok = stream.eatWhile(/[\d]/) && stream.eat(";"); + } + } else { + ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); + } + return ok ? "atom" : "error"; + } else { + stream.eatWhile(/[^&<]/); + return null; + } + } + + function inTag(stream, state) { + var ch = stream.next(); + if (ch == ">" || (ch == "/" && stream.eat(">"))) { + state.tokenize = inText; + type = ch == ">" ? "endTag" : "selfcloseTag"; + return "tag bracket"; + } else if (ch == "=") { + type = "equals"; + return null; + } else if (ch == "<") { + state.tokenize = inText; + state.state = baseState; + state.tagName = state.tagStart = null; + var next = state.tokenize(stream, state); + return next ? next + " tag error" : "tag error"; + } else if (/[\'\"]/.test(ch)) { + state.tokenize = inAttribute(ch); + state.stringStartCol = stream.column(); + return state.tokenize(stream, state); + } else { + stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/); + return "word"; + } + } + + function inAttribute(quote) { + var closure = function(stream, state) { + while (!stream.eol()) { + if (stream.next() == quote) { + state.tokenize = inTag; + break; + } + } + return "string"; + }; + closure.isInAttribute = true; + return closure; + } + + function inBlock(style, terminator) { + return function(stream, state) { + while (!stream.eol()) { + if (stream.match(terminator)) { + state.tokenize = inText; + break; + } + stream.next(); + } + return style; + }; + } + function doctype(depth) { + return function(stream, state) { + var ch; + while ((ch = stream.next()) != null) { + if (ch == "<") { + state.tokenize = doctype(depth + 1); + return state.tokenize(stream, state); + } else if (ch == ">") { + if (depth == 1) { + state.tokenize = inText; + break; + } else { + state.tokenize = doctype(depth - 1); + return state.tokenize(stream, state); + } + } + } + return "meta"; + }; + } + + function Context(state, tagName, startOfLine) { + this.prev = state.context; + this.tagName = tagName; + this.indent = state.indented; + this.startOfLine = startOfLine; + if (Kludges.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent)) + this.noIndent = true; + } + function popContext(state) { + if (state.context) state.context = state.context.prev; + } + function maybePopContext(state, nextTagName) { + var parentTagName; + while (true) { + if (!state.context) { + return; + } + parentTagName = state.context.tagName; + if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) || + !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { + return; + } + popContext(state); + } + } + + function baseState(type, stream, state) { + if (type == "openTag") { + state.tagStart = stream.column(); + return tagNameState; + } else if (type == "closeTag") { + return closeTagNameState; + } else { + return baseState; + } + } + function tagNameState(type, stream, state) { + if (type == "word") { + state.tagName = stream.current(); + setStyle = "tag"; + return attrState; + } else { + setStyle = "error"; + return tagNameState; + } + } + function closeTagNameState(type, stream, state) { + if (type == "word") { + var tagName = stream.current(); + if (state.context && state.context.tagName != tagName && + Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName)) + popContext(state); + if (state.context && state.context.tagName == tagName) { + setStyle = "tag"; + return closeState; + } else { + setStyle = "tag error"; + return closeStateErr; + } + } else { + setStyle = "error"; + return closeStateErr; + } + } + + function closeState(type, _stream, state) { + if (type != "endTag") { + setStyle = "error"; + return closeState; + } + popContext(state); + return baseState; + } + function closeStateErr(type, stream, state) { + setStyle = "error"; + return closeState(type, stream, state); + } + + function attrState(type, _stream, state) { + if (type == "word") { + setStyle = "attribute"; + return attrEqState; + } else if (type == "endTag" || type == "selfcloseTag") { + var tagName = state.tagName, tagStart = state.tagStart; + state.tagName = state.tagStart = null; + if (type == "selfcloseTag" || + Kludges.autoSelfClosers.hasOwnProperty(tagName)) { + maybePopContext(state, tagName); + } else { + maybePopContext(state, tagName); + state.context = new Context(state, tagName, tagStart == state.indented); + } + return baseState; + } + setStyle = "error"; + return attrState; + } + function attrEqState(type, stream, state) { + if (type == "equals") return attrValueState; + if (!Kludges.allowMissing) setStyle = "error"; + return attrState(type, stream, state); + } + function attrValueState(type, stream, state) { + if (type == "string") return attrContinuedState; + if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return attrState;} + setStyle = "error"; + return attrState(type, stream, state); + } + function attrContinuedState(type, stream, state) { + if (type == "string") return attrContinuedState; + return attrState(type, stream, state); + } + + return { + startState: function() { + return {tokenize: inText, + state: baseState, + indented: 0, + tagName: null, tagStart: null, + context: null}; + }, + + token: function(stream, state) { + if (!state.tagName && stream.sol()) + state.indented = stream.indentation(); + + if (stream.eatSpace()) return null; + type = null; + var style = state.tokenize(stream, state); + if ((style || type) && style != "comment") { + setStyle = null; + state.state = state.state(type || style, stream, state); + if (setStyle) + style = setStyle == "error" ? style + " error" : setStyle; + } + return style; + }, + + indent: function(state, textAfter, fullLine) { + var context = state.context; + // Indent multi-line strings (e.g. css). + if (state.tokenize.isInAttribute) { + if (state.tagStart == state.indented) + return state.stringStartCol + 1; + else + return state.indented + indentUnit; + } + if (context && context.noIndent) return CodeMirror.Pass; + if (state.tokenize != inTag && state.tokenize != inText) + return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; + // Indent the starts of attribute names. + if (state.tagName) { + if (multilineTagIndentPastTag) + return state.tagStart + state.tagName.length + 2; + else + return state.tagStart + indentUnit * multilineTagIndentFactor; + } + if (alignCDATA && /$/, + blockCommentStart: "", + + configuration: parserConfig.htmlMode ? "html" : "xml", + helperType: parserConfig.htmlMode ? "html" : "xml" + }; +}); + +CodeMirror.defineMIME("text/xml", "xml"); +CodeMirror.defineMIME("application/xml", "xml"); +if (!CodeMirror.mimeModes.hasOwnProperty("text/html")) + CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true}); + +}); diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/static/codemirror/mode/xquery/xquery.js basex-8.2.3/basex-api/src/main/webapp/dba/static/codemirror/mode/xquery/xquery.js --- basex-8.1.1/basex-api/src/main/webapp/dba/static/codemirror/mode/xquery/xquery.js 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/static/codemirror/mode/xquery/xquery.js 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,437 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("xquery", function() { + + // The keywords object is set to the result of this self executing + // function. Each keyword is a property of the keywords object whose + // value is {type: atype, style: astyle} + var keywords = function(){ + // conveinence functions used to build keywords object + function kw(type) {return {type: type, style: "keyword"};} + var A = kw("keyword a") + , B = kw("keyword b") + , C = kw("keyword c") + , operator = kw("operator") + , atom = {type: "atom", style: "atom"} + , punctuation = {type: "punctuation", style: null} + , qualifier = {type: "axis_specifier", style: "qualifier"}; + + // kwObj is what is return from this function at the end + var kwObj = { + 'if': A, 'switch': A, 'while': A, 'for': A, + 'else': B, 'then': B, 'try': B, 'finally': B, 'catch': B, + 'element': C, 'attribute': C, 'let': C, 'implements': C, 'import': C, 'module': C, 'namespace': C, + 'return': C, 'super': C, 'this': C, 'throws': C, 'where': C, 'private': C, + ',': punctuation, + 'null': atom, 'fn:false()': atom, 'fn:true()': atom + }; + + // a list of 'basic' keywords. For each add a property to kwObj with the value of + // {type: basic[i], style: "keyword"} e.g. 'after' --> {type: "after", style: "keyword"} + var basic = ['after','ancestor','ancestor-or-self','and','as','ascending','assert','attribute','before', + 'by','case','cast','child','comment','declare','default','define','descendant','descendant-or-self', + 'descending','document','document-node','element','else','eq','every','except','external','following', + 'following-sibling','follows','for','function','if','import','in','instance','intersect','item', + 'let','module','namespace','node','node','of','only','or','order','parent','precedes','preceding', + 'preceding-sibling','processing-instruction','ref','return','returns','satisfies','schema','schema-element', + 'self','some','sortby','stable','text','then','to','treat','typeswitch','union','variable','version','where', + 'xquery', 'empty-sequence']; + for(var i=0, l=basic.length; i < l; i++) { kwObj[basic[i]] = kw(basic[i]);}; + + // a list of types. For each add a property to kwObj with the value of + // {type: "atom", style: "atom"} + var types = ['xs:string', 'xs:float', 'xs:decimal', 'xs:double', 'xs:integer', 'xs:boolean', 'xs:date', 'xs:dateTime', + 'xs:time', 'xs:duration', 'xs:dayTimeDuration', 'xs:time', 'xs:yearMonthDuration', 'numeric', 'xs:hexBinary', + 'xs:base64Binary', 'xs:anyURI', 'xs:QName', 'xs:byte','xs:boolean','xs:anyURI','xf:yearMonthDuration']; + for(var i=0, l=types.length; i < l; i++) { kwObj[types[i]] = atom;}; + + // each operator will add a property to kwObj with value of {type: "operator", style: "keyword"} + var operators = ['eq', 'ne', 'lt', 'le', 'gt', 'ge', ':=', '=', '>', '>=', '<', '<=', '.', '|', '?', 'and', 'or', 'div', 'idiv', 'mod', '*', '/', '+', '-']; + for(var i=0, l=operators.length; i < l; i++) { kwObj[operators[i]] = operator;}; + + // each axis_specifiers will add a property to kwObj with value of {type: "axis_specifier", style: "qualifier"} + var axis_specifiers = ["self::", "attribute::", "child::", "descendant::", "descendant-or-self::", "parent::", + "ancestor::", "ancestor-or-self::", "following::", "preceding::", "following-sibling::", "preceding-sibling::"]; + for(var i=0, l=axis_specifiers.length; i < l; i++) { kwObj[axis_specifiers[i]] = qualifier; }; + + return kwObj; + }(); + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + + // the primary mode tokenizer + function tokenBase(stream, state) { + var ch = stream.next(), + mightBeFunction = false, + isEQName = isEQNameAhead(stream); + + // an XML tag (if not in some sub, chained tokenizer) + if (ch == "<") { + if(stream.match("!--", true)) + return chain(stream, state, tokenXMLComment); + + if(stream.match("![CDATA", false)) { + state.tokenize = tokenCDATA; + return "tag"; + } + + if(stream.match("?", false)) { + return chain(stream, state, tokenPreProcessing); + } + + var isclose = stream.eat("/"); + stream.eatSpace(); + var tagName = "", c; + while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; + + return chain(stream, state, tokenTag(tagName, isclose)); + } + // start code block + else if(ch == "{") { + pushStateStack(state,{ type: "codeblock"}); + return null; + } + // end code block + else if(ch == "}") { + popStateStack(state); + return null; + } + // if we're in an XML block + else if(isInXmlBlock(state)) { + if(ch == ">") + return "tag"; + else if(ch == "/" && stream.eat(">")) { + popStateStack(state); + return "tag"; + } + else + return "variable"; + } + // if a number + else if (/\d/.test(ch)) { + stream.match(/^\d*(?:\.\d*)?(?:E[+\-]?\d+)?/); + return "atom"; + } + // comment start + else if (ch === "(" && stream.eat(":")) { + pushStateStack(state, { type: "comment"}); + return chain(stream, state, tokenComment); + } + // quoted string + else if ( !isEQName && (ch === '"' || ch === "'")) + return chain(stream, state, tokenString(ch)); + // variable + else if(ch === "$") { + return chain(stream, state, tokenVariable); + } + // assignment + else if(ch ===":" && stream.eat("=")) { + return "keyword"; + } + // open paren + else if(ch === "(") { + pushStateStack(state, { type: "paren"}); + return null; + } + // close paren + else if(ch === ")") { + popStateStack(state); + return null; + } + // open paren + else if(ch === "[") { + pushStateStack(state, { type: "bracket"}); + return null; + } + // close paren + else if(ch === "]") { + popStateStack(state); + return null; + } + else { + var known = keywords.propertyIsEnumerable(ch) && keywords[ch]; + + // if there's a EQName ahead, consume the rest of the string portion, it's likely a function + if(isEQName && ch === '\"') while(stream.next() !== '"'){} + if(isEQName && ch === '\'') while(stream.next() !== '\''){} + + // gobble up a word if the character is not known + if(!known) stream.eatWhile(/[\w\$_-]/); + + // gobble a colon in the case that is a lib func type call fn:doc + var foundColon = stream.eat(":"); + + // if there's not a second colon, gobble another word. Otherwise, it's probably an axis specifier + // which should get matched as a keyword + if(!stream.eat(":") && foundColon) { + stream.eatWhile(/[\w\$_-]/); + } + // if the next non whitespace character is an open paren, this is probably a function (if not a keyword of other sort) + if(stream.match(/^[ \t]*\(/, false)) { + mightBeFunction = true; + } + // is the word a keyword? + var word = stream.current(); + known = keywords.propertyIsEnumerable(word) && keywords[word]; + + // if we think it's a function call but not yet known, + // set style to variable for now for lack of something better + if(mightBeFunction && !known) known = {type: "function_call", style: "variable def"}; + + // if the previous word was element, attribute, axis specifier, this word should be the name of that + if(isInXmlConstructor(state)) { + popStateStack(state); + return "variable"; + } + // as previously checked, if the word is element,attribute, axis specifier, call it an "xmlconstructor" and + // push the stack so we know to look for it on the next word + if(word == "element" || word == "attribute" || known.type == "axis_specifier") pushStateStack(state, {type: "xmlconstructor"}); + + // if the word is known, return the details of that else just call this a generic 'word' + return known ? known.style : "variable"; + } + } + + // handle comments, including nested + function tokenComment(stream, state) { + var maybeEnd = false, maybeNested = false, nestedCount = 0, ch; + while (ch = stream.next()) { + if (ch == ")" && maybeEnd) { + if(nestedCount > 0) + nestedCount--; + else { + popStateStack(state); + break; + } + } + else if(ch == ":" && maybeNested) { + nestedCount++; + } + maybeEnd = (ch == ":"); + maybeNested = (ch == "("); + } + + return "comment"; + } + + // tokenizer for string literals + // optionally pass a tokenizer function to set state.tokenize back to when finished + function tokenString(quote, f) { + return function(stream, state) { + var ch; + + if(isInString(state) && stream.current() == quote) { + popStateStack(state); + if(f) state.tokenize = f; + return "string"; + } + + pushStateStack(state, { type: "string", name: quote, tokenize: tokenString(quote, f) }); + + // if we're in a string and in an XML block, allow an embedded code block + if(stream.match("{", false) && isInXmlAttributeBlock(state)) { + state.tokenize = tokenBase; + return "string"; + } + + + while (ch = stream.next()) { + if (ch == quote) { + popStateStack(state); + if(f) state.tokenize = f; + break; + } + else { + // if we're in a string and in an XML block, allow an embedded code block in an attribute + if(stream.match("{", false) && isInXmlAttributeBlock(state)) { + state.tokenize = tokenBase; + return "string"; + } + + } + } + + return "string"; + }; + } + + // tokenizer for variables + function tokenVariable(stream, state) { + var isVariableChar = /[\w\$_-]/; + + // a variable may start with a quoted EQName so if the next character is quote, consume to the next quote + if(stream.eat("\"")) { + while(stream.next() !== '\"'){}; + stream.eat(":"); + } else { + stream.eatWhile(isVariableChar); + if(!stream.match(":=", false)) stream.eat(":"); + } + stream.eatWhile(isVariableChar); + state.tokenize = tokenBase; + return "variable"; + } + + // tokenizer for XML tags + function tokenTag(name, isclose) { + return function(stream, state) { + stream.eatSpace(); + if(isclose && stream.eat(">")) { + popStateStack(state); + state.tokenize = tokenBase; + return "tag"; + } + // self closing tag without attributes? + if(!stream.eat("/")) + pushStateStack(state, { type: "tag", name: name, tokenize: tokenBase}); + if(!stream.eat(">")) { + state.tokenize = tokenAttribute; + return "tag"; + } + else { + state.tokenize = tokenBase; + } + return "tag"; + }; + } + + // tokenizer for XML attributes + function tokenAttribute(stream, state) { + var ch = stream.next(); + + if(ch == "/" && stream.eat(">")) { + if(isInXmlAttributeBlock(state)) popStateStack(state); + if(isInXmlBlock(state)) popStateStack(state); + return "tag"; + } + if(ch == ">") { + if(isInXmlAttributeBlock(state)) popStateStack(state); + return "tag"; + } + if(ch == "=") + return null; + // quoted string + if (ch == '"' || ch == "'") + return chain(stream, state, tokenString(ch, tokenAttribute)); + + if(!isInXmlAttributeBlock(state)) + pushStateStack(state, { type: "attribute", tokenize: tokenAttribute}); + + stream.eat(/[a-zA-Z_:]/); + stream.eatWhile(/[-a-zA-Z0-9_:.]/); + stream.eatSpace(); + + // the case where the attribute has not value and the tag was closed + if(stream.match(">", false) || stream.match("/", false)) { + popStateStack(state); + state.tokenize = tokenBase; + } + + return "attribute"; + } + + // handle comments, including nested + function tokenXMLComment(stream, state) { + var ch; + while (ch = stream.next()) { + if (ch == "-" && stream.match("->", true)) { + state.tokenize = tokenBase; + return "comment"; + } + } + } + + + // handle CDATA + function tokenCDATA(stream, state) { + var ch; + while (ch = stream.next()) { + if (ch == "]" && stream.match("]", true)) { + state.tokenize = tokenBase; + return "comment"; + } + } + } + + // handle preprocessing instructions + function tokenPreProcessing(stream, state) { + var ch; + while (ch = stream.next()) { + if (ch == "?" && stream.match(">", true)) { + state.tokenize = tokenBase; + return "comment meta"; + } + } + } + + + // functions to test the current context of the state + function isInXmlBlock(state) { return isIn(state, "tag"); } + function isInXmlAttributeBlock(state) { return isIn(state, "attribute"); } + function isInXmlConstructor(state) { return isIn(state, "xmlconstructor"); } + function isInString(state) { return isIn(state, "string"); } + + function isEQNameAhead(stream) { + // assume we've already eaten a quote (") + if(stream.current() === '"') + return stream.match(/^[^\"]+\"\:/, false); + else if(stream.current() === '\'') + return stream.match(/^[^\"]+\'\:/, false); + else + return false; + } + + function isIn(state, type) { + return (state.stack.length && state.stack[state.stack.length - 1].type == type); + } + + function pushStateStack(state, newState) { + state.stack.push(newState); + } + + function popStateStack(state) { + state.stack.pop(); + var reinstateTokenize = state.stack.length && state.stack[state.stack.length-1].tokenize; + state.tokenize = reinstateTokenize || tokenBase; + } + + // the interface for the mode API + return { + startState: function() { + return { + tokenize: tokenBase, + cc: [], + stack: [] + }; + }, + + token: function(stream, state) { + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + return style; + }, + + blockCommentStart: "(:", + blockCommentEnd: ":)" + + }; + +}); + +CodeMirror.defineMIME("application/xquery", "xquery"); + +}); diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/static/editor.js basex-8.2.3/basex-api/src/main/webapp/dba/static/editor.js --- basex-8.1.1/basex-api/src/main/webapp/dba/static/editor.js 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/static/editor.js 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,124 @@ +function openQuery() { + var file = document.getElementById("file"); + request("POST", "open-query?name=" + encodeURIComponent(file.value.trim()), + null, + function(req) { + document.getElementById("editor").value = req.responseText; + var evt = document.createEvent("HTMLEvents"); + evt.initEvent("change",false,false); + document.getElementById("editor").dispatchEvent(evt); + }, + function(req) { + setError('Query could not be opened.'); + } + ) +}; + +function saveQuery() { + if(queryExists() && !confirm('Overwrite existing query?')) return; + + var file = document.getElementById("file"); + request("POST", "save-query?name=" + encodeURIComponent(file.value.trim()), + document.getElementById("editor").value, + function(req) { + refreshDataList(req); + setInfo("Query was saved."); + }, + function(req) { + setError("Query could not be saved."); + } + ) +}; + +function deleteQuery() { + if(!confirm('Are you sure?')) return; + + var file = document.getElementById("file"); + request("POST", "delete-query?name=" + encodeURIComponent(file.value.trim()), + null, + function(req) { + refreshDataList(req); + setInfo("Query was deleted"); + file.value = ""; + }, + function(req) { + setError("Query could not be deleted."); + } + ) +}; + +function refreshDataList(req) { + var files = document.getElementById("files"); + while(files.firstChild) { + files.removeChild(files.firstChild); + } + var names = req.responseText.split('/'); + for (var i = 0; i < names.length; i++) { + var opt = document.createElement('option'); + opt.value = names[i]; + files.appendChild(opt); + } + checkButtons(); +}; + +function checkButtons() { + var file = document.getElementById("file").value.trim(); + document.getElementById("save").disabled = file.length == 0; + var disable = !queryExists(); + document.getElementById("open").disabled = disable; + document.getElementById("delete").disabled = disable; +}; + +function queryExists() { + var file = document.getElementById("file").value.trim(); + var files = document.getElementById("files").children; + for (var i = 0; i < files.length; i++) { + if(files[i].value == file) return true; + } + return false; +}; + +function loadCodeMirror() { + if (CodeMirror && dispatchEvent) { + //Now replace the editor and result areas + var editorArea = document.getElementById("editor"); + var codeMirrorEditor = CodeMirror.fromTextArea(editorArea, { + mode: "xquery", + lineNumbers: true, + extraKeys: { + "Ctrl-Enter": function(cm) { + editor("Please wait…", "Query was successful.", true); + }, + "Cmd-Enter": function(cm) { + editor("Please wait…", "Query was successful.", true); + } + } + }); + codeMirrorEditor.on("change",function(cm, cmo) { + cm.save(); + editor("Please wait…", "Query was successful.", false); + } + ); + codeMirrorEditor.display.wrapper.style.border = "solid 1pt black"; + editorArea.addEventListener("change",function() { + codeMirrorEditor.setValue(editorArea.value); + }); + var outputArea = document.getElementById("output"); + var codeMirrorResult = CodeMirror.fromTextArea(outputArea, {mode: "xml"}); + codeMirrorResult.display.wrapper.style.border = "solid 1pt black"; + outputArea.addEventListener("change",function() {codeMirrorResult.setValue(outputArea.value)}); + window.addEventListener("load",setDisplayHeight); + window.addEventListener("resize",setDisplayHeight); + } +} + +function setDisplayHeight() { + var elem = document.createElement("div"); + document.body.appendChild(elem); + p = elem.offsetTop + 48 //To account for margin, it works but is not bullet proof. + var c = document.getElementsByClassName("CodeMirror")[0].offsetHeight; + var s = window.innerHeight; + Array.prototype.forEach.call(document.getElementsByClassName("CodeMirror"),function(cm) { + cm.CodeMirror.setSize("100%",Math.max(200,s-(p-c))); + }); +} diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/static/js.js basex-8.2.3/basex-api/src/main/webapp/dba/static/js.js --- basex-8.1.1/basex-api/src/main/webapp/dba/static/js.js 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/static/js.js 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,155 @@ +function buttons() { + var forms = document.getElementsByTagName("form"); + for(var f = 0; f < forms.length; f++) { + var form = forms[f]; + if(form.className != 'update') continue; + + var inputs = form.getElementsByTagName("input"); + var c = 0; + for(var i = 0; i < inputs.length; i++) { + if(inputs[i].type == "checkbox" && inputs[i].checked) c++; + } + var buttons = form.getElementsByTagName("button"); + for(var i = 0; i < buttons.length; i++) { + var button = buttons[i], n = button.className, s = button.value, e = !button.disabled; + if(n == 'global') continue; + + if(s == "optimize" || s == "optimize-all" || s == "drop-backup" || + s == "drop-db" || s == "drop-pattern" || s == "drop-user" || + s == "kill-session" || s == "restore" || s == "backup" || s == "delete" || + s == "delete-files" || s == "delete-logs" || s == "kill") { + e = c > 0; + } + button.disabled = !e; + } + } +}; + +function setInfo(message) { + setText(message, 'info'); +}; + +function setWarning(message) { + setText(message, 'warning'); +}; + +function setError(message) { + setText(message.replace(/Stack Trace:.*/, ''), 'error'); +}; + +function setText(message, type) { + var i = document.getElementById("info"); + i.className = type; + i.textContent = message; +}; + +var searchDelay = 200; +var _d; +function query(wait, success, key, query, enforce, target) { + var d = new Date(); + _d = d; + setTimeout(function() { + if(_d != d) return; + if(wait) setWarning(wait); + + var name = document.getElementById("name"); + var resource = document.getElementById("resource"); + var sort = document.getElementById("sort"); + var loglist = document.getElementById("loglist"); + var url = key + + "?name=" + encodeURIComponent(name ? name.value : "") + + "&resource=" + encodeURIComponent(resource ? resource.value : "") + + "&sort=" + encodeURIComponent(sort ? sort.value : "") + + "&loglist=" + encodeURIComponent(loglist ? loglist.value : ""); + request("POST", url, query, + function(req) { + if(_d != d) return; + target(req.responseText); + if(success) setInfo(success); + }, + function(req) { + if(_d != d) return; + setErrorFromResponse(req); + } + ) + }, enforce ? 0 : searchDelay); +}; + +// Jetty and Tomcat support (both return error messages differently) +function setErrorFromResponse(req) { + var msg = req.statusText.match(/\[\w+\]/g) ? req.statusText : req.responseText; + var s = msg.indexOf('['), e1 = msg.indexOf('\n', s), e2 = msg.indexOf('<', s); + if(s > -1) msg = msg.substring(s, e1 > e2 ? e2 : e1 > s ? e1 : msg.length); + var html = document.createElement('div'); + html.innerHTML = msg; + setError(html.innerText || html.textContent); +}; + +var _list; +function logslist(wait, success) { + var input = document.getElementById('loglist').value.trim(); + if(_list == input) return false; + _list = input; + query(wait, success, 'loglist', input, false, function(text) { + document.getElementById("list").innerHTML = text; + }) +}; + +var _logs; +function logentries(wait, success) { + var input = document.getElementById('logs').value.trim(); + if(_logs == input) return false; + _logs = input; + query(wait, success, 'log', input, false, function(text) { + document.getElementById("output").innerHTML = text; + }); +}; + +var _input; +function queryResource(wait, success) { + var input = document.getElementById('input').value.trim(); + if(_input == input) return false; + _input = input; + var target = 'query-resource'; + var enforce = false; + query(wait, success, target, input, enforce, function(text) { + document.getElementById("output").value = text; + }); +}; + +var _editor; +function editor(wait, success, enforce) { + var mode = document.getElementById("mode").selectedIndex; + var update = mode == 2; + var realtime = mode == 1; + document.getElementById("run").disabled = realtime; + + var editor = document.getElementById('editor').value; + if(enforce || (realtime && _editor != editor)) { + _editor = editor; + var target = update ? 'update-query' : 'eval-query'; + query(wait, success, target, editor, enforce, function(text) { + document.getElementById("output").value = text; + var evt = document.createEvent("HTMLEvents"); + evt.initEvent("change",false,false); + document.getElementById("output").dispatchEvent(evt); + }); + } +}; + +function request(method, url, data, success, failure) { + var req = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); + req.onreadystatechange = function() { + if(req.readyState == 4) { + if(req.status == 200) { + success(req); + } else { + failure(req); + } + } + }; + // synchronous querying: wait for server feedback + req.open(method, url, true); + req.setRequestHeader("Content-Type", "text/plain"); + req.send(data); +}; diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/static/style.css basex-8.2.3/basex-api/src/main/webapp/dba/static/style.css --- basex-8.1.1/basex-api/src/main/webapp/dba/static/style.css 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/static/style.css 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,142 @@ +body { + font-family: Calibri, "Trebuchet MS", sans-serif; + overflow-y: scroll; + margin: 1em; + font-size: 16px; +} + +ul, dd { + padding-left: 1.2em; + margin-left: 0; +} + +dl { + margin: 0; +} + +dt { + margin: 0.6em 0 0.6em 0; + font-weight: bold; +} + +table { + font-size: 100%; + padding: 0; + border: 0; + table-layout: fixed; +} + +th { + padding: 0 1.2em .2em 0; +} + +td { + padding: 0 1.2em 0 0; + vertical-align: top; + border: 0; +} + +textarea { + font-family: "Consolas", "Ubuntu Mono", monospace; + font-size: 90%; + margin: 4px 0px 4px 0px; + width: 100%; +} + +select { + width: 166; +} + +code { + font-family: "Consolas", "Ubuntu Mono", monospace; + border: 1px solid #E3E3FF; + border-radius: 3px; + padding: 0 4px 0 4px; + margin: 0 2px 0 2px; + font-size: 90%; +} + +pre { + font-family: "Consolas", "Ubuntu Mono", monospace; + font-size: 90%; +} + +h1 { + font-size: 220%; + font-weight: bold; + color: #AA1818; + margin: -2px 0 0 0; +} + +h2 { + font-size: 150%; + color: #CC3333; + margin: 0 0 0.2em 0; +} + +h3 { + font-size: 120%; + color: #CC3333; + margin: 0.2em 0 0.2em 0; +} + +h4 { + font-size: 100%; + margin: 0 0 0.4em 0; +} + +hr { + height: 1px; + border: 0px; + background-color: #777; +} + +a { + color: #CC3333; + text-decoration: none; +} + +a:hover { + text-decoration:underline; +} + +p { + margin: 0 0 0.6em 0; +} + +.right { + float: right; +} + +.error { + color: #FF3333; + font-weight: bold; +} + +.info { + color: #009900; + font-weight: bold; +} + +.warning { + color: #999999; + font-weight: bold; +} + +.note { + color: #666666; +} + +.small { + height: 10px; +} + +.variable { + font-family: "Calibri", "Trebuchet MS", sans-serif; +} + +.vertical { + width: 1px; + border-left: 1px solid #000000; + height: 100%; +} diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/users/add-pattern.xqm basex-8.2.3/basex-api/src/main/webapp/dba/users/add-pattern.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/users/add-pattern.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/users/add-pattern.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -24,7 +24,7 @@ :) declare %rest:GET - %rest:path("dba/add-pattern") + %rest:path("/dba/add-pattern") %rest:query-param("name", "{$name}") %rest:query-param("pattern", "{$pattern}") %rest:query-param("perm", "{$perm}", "write") @@ -83,7 +83,7 @@ declare %updating %rest:POST - %rest:path("dba/add-pattern") + %rest:path("/dba/add-pattern") %rest:query-param("name", "{$name}") %rest:query-param("perm", "{$perm}") %rest:query-param("pattern", "{$pattern}") diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/users/create-user.xqm basex-8.2.3/basex-api/src/main/webapp/dba/users/create-user.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/users/create-user.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/users/create-user.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -22,7 +22,7 @@ :) declare %rest:GET - %rest:path("dba/create-user") + %rest:path("/dba/create-user") %rest:query-param("name", "{$name}") %rest:query-param("pw", "{$pw}") %rest:query-param("perm", "{$perm}", "none") @@ -91,7 +91,7 @@ declare %updating %rest:POST - %rest:path("dba/create-user") + %rest:path("/dba/create-user") %rest:query-param("name", "{$name}") %rest:query-param("pw", "{$pw}") %rest:query-param("perm", "{$perm}") diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/users/drop-pattern.xqm basex-8.2.3/basex-api/src/main/webapp/dba/users/drop-pattern.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/users/drop-pattern.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/users/drop-pattern.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -19,7 +19,7 @@ declare %updating %rest:GET - %rest:path("dba/drop-pattern") + %rest:path("/dba/drop-pattern") %rest:query-param("name", "{$name}") %rest:query-param("pattern", "{$pattern}") %output:method("html") diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/users/drop-user.xqm basex-8.2.3/basex-api/src/main/webapp/dba/users/drop-user.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/users/drop-user.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/users/drop-user.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -18,7 +18,7 @@ declare %updating %rest:GET - %rest:path("dba/drop-user") + %rest:path("/dba/drop-user") %rest:query-param("name", "{$names}") %output:method("html") function _:drop( diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/users/kill.xqm basex-8.2.3/basex-api/src/main/webapp/dba/users/kill.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/users/kill.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/users/kill.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -17,7 +17,7 @@ :) declare %rest:GET - %rest:path("dba/kill-dba") + %rest:path("/dba/kill-dba") %rest:query-param("id", "{$ids}") %output:method("html") function _:drop( diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/users/users.xqm basex-8.2.3/basex-api/src/main/webapp/dba/users/users.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/users/users.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/users/users.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -24,7 +24,7 @@ :) declare %rest:GET - %rest:path("dba/users") + %rest:path("/dba/users") %rest:query-param("sort", "{$sort}", "") %rest:query-param("error", "{$error}") %rest:query-param("info", "{$info}") @@ -114,7 +114,7 @@ :) declare %rest:POST - %rest:path("dba/users") + %rest:path("/dba/users") %rest:query-param("action", "{$action}") %rest:query-param("name", "{$names}") %rest:query-param("id", "{$ids}") diff -Nru basex-8.1.1/basex-api/src/main/webapp/dba/users/user.xqm basex-8.2.3/basex-api/src/main/webapp/dba/users/user.xqm --- basex-8.1.1/basex-api/src/main/webapp/dba/users/user.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/dba/users/user.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -27,7 +27,7 @@ :) declare %rest:GET - %rest:path("dba/user") + %rest:path("/dba/user") %rest:query-param("name", "{$name}") %rest:query-param("newname", "{$newname}") %rest:query-param("pw", "{$pw}") @@ -149,7 +149,7 @@ :) declare %rest:POST - %rest:path("dba/user") + %rest:path("/dba/user") %rest:form-param("action", "{$action}") %rest:form-param("name", "{$name}") %rest:form-param("pattern", "{$pattern}") @@ -172,7 +172,7 @@ declare %updating %rest:POST - %rest:path("dba/edit-user") + %rest:path("/dba/edit-user") %rest:query-param("name", "{$name}") %rest:query-param("newname", "{$newname}") %rest:query-param("pw", "{$pw}") diff -Nru basex-8.1.1/basex-api/src/main/webapp/WEB-INF/web.xml basex-8.2.3/basex-api/src/main/webapp/WEB-INF/web.xml --- basex-8.1.1/basex-api/src/main/webapp/WEB-INF/web.xml 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/main/webapp/WEB-INF/web.xml 2015-07-14 10:54:40.000000000 +0000 @@ -53,9 +53,10 @@ --> - + org.basex.http.SessionListener + org.basex.http.ServletListener diff -Nru basex-8.1.1/basex-api/src/test/java/org/basex/AdvancedQueryTest.java basex-8.2.3/basex-api/src/test/java/org/basex/AdvancedQueryTest.java --- basex-8.1.1/basex-api/src/test/java/org/basex/AdvancedQueryTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/test/java/org/basex/AdvancedQueryTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -22,7 +22,7 @@ try(final QueryProcessor qp = new QueryProcessor(query, context)) { final ArrayOutput ao = new ArrayOutput(); try(final Serializer ser = qp.getSerializer(ao)) { - qp.execute().serialize(ser); + qp.value().serialize(ser); } return ao.toString().replaceAll("(\\r|\\n)\\s*", ""); } catch(final Exception ex) { diff -Nru basex-8.1.1/basex-api/src/test/java/org/basex/api/xmldb/XMLDBBaseTest.java basex-8.2.3/basex-api/src/test/java/org/basex/api/xmldb/XMLDBBaseTest.java --- basex-8.1.1/basex-api/src/test/java/org/basex/api/xmldb/XMLDBBaseTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/test/java/org/basex/api/xmldb/XMLDBBaseTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -21,7 +21,7 @@ static final String DRIVER = BXDatabase.class.getName(); /** Database/document path. */ static final String URL = - "xmldb:" + Prop.PROJECT_NAME + "://" + S_LOCALHOST + ':' + StaticOptions.PORT.value + '/'; + "xmldb:" + Prop.PROJECT_NAME + "://" + S_LOCALHOST + ':' + DB_PORT + '/'; /** Name of the collection. */ static final String COLL = "XMLDB"; /** Database/document path. */ @@ -37,16 +37,13 @@ /** Test document. */ static final String DOC3 = "third.xml"; - /** Context. */ - private static final Context CONTEXT = new Context(); - /** * Create XMLDB database. * @throws BaseXException exception during database create */ static void createDB() throws BaseXException { - new CreateDB(COLL, DOCPATH + DOC1).execute(CONTEXT); - new Close().execute(CONTEXT); + new CreateDB(COLL, DOCPATH + DOC1).execute(context); + new Close().execute(context); } /** @@ -54,6 +51,6 @@ * @throws BaseXException exception during database drop */ static void dropDB() throws BaseXException { - new DropDB(COLL).execute(CONTEXT); + new DropDB(COLL).execute(context); } } diff -Nru basex-8.1.1/basex-api/src/test/java/org/basex/http/FnHttpTest.java basex-8.2.3/basex-api/src/test/java/org/basex/http/FnHttpTest.java --- basex-8.1.1/basex-api/src/test/java/org/basex/http/FnHttpTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/test/java/org/basex/http/FnHttpTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -14,19 +14,19 @@ import org.basex.core.*; import org.basex.core.cmd.*; -import org.basex.data.*; import org.basex.io.*; import org.basex.io.serial.*; +import org.basex.query.QueryError.ErrType; import org.basex.query.*; -import org.basex.query.QueryError.*; import org.basex.query.func.fn.*; -import org.basex.query.iter.*; +import org.basex.query.util.list.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.query.value.type.*; import org.basex.util.*; import org.basex.util.http.*; -import org.basex.util.http.HttpRequest.*; +import org.basex.util.http.HttpRequest.Part; import org.junit.*; import org.junit.Test; @@ -72,7 +72,7 @@ "" + "" + BOOKS + "" + "", RESTURL), ctx)) { - checkResponse(qp.execute(), 1, HttpURLConnection.HTTP_CREATED); + checkResponse(qp.value(), 1, HttpURLConnection.HTTP_CREATED); } } @@ -87,7 +87,7 @@ "" + "" + BOOKS + "" + "", RESTURL), ctx)) { - checkResponse(qp.execute(), 1, HttpURLConnection.HTTP_CREATED); + checkResponse(qp.value(), 1, HttpURLConnection.HTTP_CREATED); } // POST - query @@ -99,7 +99,7 @@ + "" + "" + "", RESTURL), ctx)) { - checkResponse(qp.execute(), 2, HttpURLConnection.HTTP_OK); + checkResponse(qp.value(), 2, HttpURLConnection.HTTP_OK); } // Execute the same query but with content set from $bodies @@ -111,7 +111,7 @@ "" + "1]]>" + ""), ctx)) { - checkResponse(qp.execute(), 2, HttpURLConnection.HTTP_OK); + checkResponse(qp.value(), 2, HttpURLConnection.HTTP_OK); } } @@ -124,25 +124,25 @@ // GET1 - just send a GET request try(final QueryProcessor qp = new QueryProcessor(_HTTP_SEND_REQUEST.args( ""), ctx)) { - final Result r = qp.execute(); - checkResponse(r, 2, HttpURLConnection.HTTP_OK); + final Value v = qp.value(); + checkResponse(v, 2, HttpURLConnection.HTTP_OK); - assertEquals(NodeType.DOC, ((Iter) r).get(1).type); + assertEquals(NodeType.DOC, v.itemAt(1).type); } // GET2 - with override-media-type='text/plain' try(final QueryProcessor qp = new QueryProcessor(_HTTP_SEND_REQUEST.args( "", REST_ROOT), ctx)) { - final Result r = qp.execute(); - checkResponse(r, 2, HttpURLConnection.HTTP_OK); + final Value v = qp.value(); + checkResponse(v, 2, HttpURLConnection.HTTP_OK); - assertEquals(AtomType.STR, ((Iter) r).get(1).type); + assertEquals(AtomType.STR, v.itemAt(1).type); } // Get3 - with status-only='true' try(final QueryProcessor qp = new QueryProcessor(_HTTP_SEND_REQUEST.args( "", REST_ROOT), ctx)) { - checkResponse(qp.execute(), 1, HttpURLConnection.HTTP_OK); + checkResponse(qp.value(), 1, HttpURLConnection.HTTP_OK); } } @@ -157,13 +157,13 @@ "" + "" + "", RESTURL), ctx)) { - qp.execute(); + qp.value(); } // DELETE try(final QueryProcessor qp = new QueryProcessor(_HTTP_SEND_REQUEST.args( "", RESTURL), ctx)) { - checkResponse(qp.execute(), 1, HttpURLConnection.HTTP_OK); + checkResponse(qp.value(), 1, HttpURLConnection.HTTP_OK); } } @@ -185,9 +185,9 @@ */ @Test public void sendReqNoParams() { - final Command c = new XQuery(_HTTP_SEND_REQUEST.args("()")); + final Command cmd = new XQuery(_HTTP_SEND_REQUEST.args("()")); try { - c.execute(ctx); + cmd.execute(ctx); } catch(final BaseXException ex) { assertTrue(ex.getMessage().contains(ErrType.HC.toString())); } @@ -195,13 +195,13 @@ /** * Tests an erroneous query. - * @throws QueryException query exception + * @throws Exception exception */ @Test - public void error() throws QueryException { + public void error() throws Exception { try(final QueryProcessor qp = new QueryProcessor(_HTTP_SEND_REQUEST.args( "", RESTURL + "unknown") + "[1]/@status/data()", ctx)) { - assertEquals("404", qp.execute().toString()); + assertEquals("404", qp.value().serialize().toString()); } } @@ -302,13 +302,13 @@ + "" + ""; final DBNode dbNode1 = new DBNode(new IOContent(multiReq)); - final ValueBuilder bodies = new ValueBuilder(); + final ItemList bodies = new ItemList(); bodies.add(Str.get("Part1")); bodies.add(Str.get("Part2")); bodies.add(Str.get("Part3")); final HttpRequestParser rp = new HttpRequestParser(null); - final HttpRequest r = rp.parse(dbNode1.children().next(), bodies); + final HttpRequest r = rp.parse(dbNode1.children().next(), bodies.iter()); assertEquals(2, r.attributes.size()); assertEquals(2, r.headers.size()); @@ -344,14 +344,15 @@ ""), ctx)) { - checkResponse(qp.execute(), 2, HttpURLConnection.HTTP_OK); + checkResponse(qp.value(), 2, HttpURLConnection.HTTP_OK); } // wrong credentials try(final QueryProcessor qp = new QueryProcessor(_HTTP_SEND_REQUEST.args( - ""), ctx)) { - checkResponse(qp.execute(), 1, HttpURLConnection.HTTP_UNAUTHORIZED); + "") + + "[. instance of node()][@status = '401']", ctx)) { + checkResponse(qp.value(), 1, HttpURLConnection.HTTP_UNAUTHORIZED); } } @@ -366,14 +367,15 @@ ""), ctx)) { - checkResponse(qp.execute(), 2, HttpURLConnection.HTTP_OK); + checkResponse(qp.value(), 2, HttpURLConnection.HTTP_OK); } // wrong credentials try(final QueryProcessor qp = new QueryProcessor(_HTTP_SEND_REQUEST.args( ""), ctx)) { - checkResponse(qp.execute(), 1, HttpURLConnection.HTTP_UNAUTHORIZED); + "href='" + REST_ROOT + "?query=()'/>") + + "[. instance of node()][@status = '401']", ctx)) { + checkResponse(qp.value(), 1, HttpURLConnection.HTTP_UNAUTHORIZED); } } @@ -641,9 +643,9 @@ // set content encoded in CP1251 final String test = "\u0442\u0435\u0441\u0442"; conn.content = Charset.forName("CP1251").encode(test).array(); - final Iter i = new HttpResponse(null, ctx.options).getResponse(conn, true, null); + final ItemList res = new HttpResponse(null, ctx.options).getResponse(conn, true, null); // compare results - assertEquals(test, string(i.get(1).string(null))); + assertEquals(test, string(res.get(1).string(null))); } /** @@ -683,10 +685,10 @@ + ".... richtext..." + CRLF + "--boundary42" + CRLF + "Content-Type: text/x-whatever" + CRLF + CRLF + ".... fanciest formatted version " + CRLF + "..." + CRLF + "--boundary42--"); - final ValueIter returned = new HttpResponse(null, ctx.options).getResponse(conn, true, null); + final ItemList returned = new HttpResponse(null, ctx.options).getResponse(conn, true, null); // Construct expected result - final ValueBuilder expected = new ValueBuilder(); + final ItemList expected = new ItemList(); final String response = "" @@ -761,10 +763,10 @@ + CRLF + "--simple boundary--" + CRLF + "This is the epilogue. It is also to be ignored."); // Get response as sequence of XQuery items - final ValueIter returned = new HttpResponse(null, ctx.options).getResponse(conn, true, null); + final ItemList returned = new HttpResponse(null, ctx.options).getResponse(conn, true, null); // Construct expected result - final ValueBuilder expected = new ValueBuilder(); + final ItemList expected = new ItemList(); final String response = "" @@ -798,8 +800,7 @@ * @param returned returned result * @throws Exception exception */ - private static void compare(final ValueBuilder expected, final ValueIter returned) - throws Exception { + private static void compare(final ItemList expected, final ItemList returned) throws Exception { // Compare response with expected result assertEquals("Different number of results", expected.size(), returned.size()); @@ -841,19 +842,14 @@ /** * Checks the response to an HTTP request. - * @param r query result + * @param v query result * @param itemsCount expected number of items * @param expStatus expected status - * @throws QueryException query exception */ - private static void checkResponse(final Result r, final int itemsCount, final int expStatus) - throws QueryException { - - assertTrue(r instanceof Iter); - final Iter res = (Iter) r; - assertEquals(itemsCount, r.size()); - assertTrue(res.get(0) instanceof FElem); - final FElem response = (FElem) res.get(0); + private static void checkResponse(final Value v, final int itemsCount, final int expStatus) { + assertEquals(itemsCount, v.size()); + assertTrue(v.itemAt(0) instanceof FElem); + final FElem response = (FElem) v.itemAt(0); assertNotNull(response.attributes()); if(!eq(response.attribute(STATUS), token(expStatus))) { fail("Expected: " + expStatus + "\nFound: " + response); diff -Nru basex-8.1.1/basex-api/src/test/java/org/basex/http/HTTPTest.java basex-8.2.3/basex-api/src/test/java/org/basex/http/HTTPTest.java --- basex-8.1.1/basex-api/src/test/java/org/basex/http/HTTPTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/test/java/org/basex/http/HTTPTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -58,7 +58,7 @@ final StringList sl = new StringList(); if(local) sl.add("-l"); - sl.add("-p" + DB_PORT, "-e" + EVENT_PORT, "-h" + HTTP_PORT, "-s" + STOP_PORT, "-z"); + sl.add("-p" + DB_PORT, "-h" + HTTP_PORT, "-s" + STOP_PORT, "-z"); sl.add("-U" + ADMIN, "-P" + ADMIN); System.setOut(NULL); try { diff -Nru basex-8.1.1/basex-api/src/test/java/org/basex/http/rest/RESTGetTest.java basex-8.2.3/basex-api/src/test/java/org/basex/http/rest/RESTGetTest.java --- basex-8.1.1/basex-api/src/test/java/org/basex/http/rest/RESTGetTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/test/java/org/basex/http/rest/RESTGetTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -116,9 +116,7 @@ */ @Test public void queryOption() throws IOException { - assertEquals("2", - get("?query=2,delete+node+&" + MainOptions.MIXUPDATES.name() + "=true") - ); + assertEquals("2", get("?query=2,delete+node+&" + MainOptions.MIXUPDATES.name() + "=true")); try { get("?query=1,delete+node+&" + MainOptions.MIXUPDATES.name() + "=false"); fail("Error expected."); diff -Nru basex-8.1.1/basex-api/src/test/java/org/basex/SandboxTest.java basex-8.2.3/basex-api/src/test/java/org/basex/SandboxTest.java --- basex-8.1.1/basex-api/src/test/java/org/basex/SandboxTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/test/java/org/basex/SandboxTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,19 +1,6 @@ package org.basex; -import static org.basex.core.Text.*; -import static org.junit.Assert.*; - -import java.io.*; -import java.util.concurrent.*; - -import org.basex.api.client.*; import org.basex.core.*; -import org.basex.core.users.*; -import org.basex.io.*; -import org.basex.io.out.*; -import org.basex.util.*; -import org.basex.util.list.*; -import org.basex.util.options.*; import org.junit.*; /** @@ -22,166 +9,20 @@ * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ -public abstract class SandboxTest { - /** Database port. */ - protected static final int DB_PORT = 9996; - /** Event port. */ - protected static final int EVENT_PORT = 9997; - - /** Default output stream. */ - public static final PrintStream OUT = System.out; - /** Default error stream. */ - public static final PrintStream ERR = System.err; - /** Null output stream. */ - public static final PrintStream NULL = new PrintStream(new NullOutput()); - /** Test name. */ - protected static final String NAME = Util.className(SandboxTest.class); - /** Database context. */ - protected static Context context; - +public abstract class SandboxTest extends Sandbox { /** * Creates the sandbox. */ @BeforeClass - public static void initSandbox() { - final IOFile sb = sandbox(); - sb.delete(); - assertTrue("Sandbox could not be created.", sb.md()); - context = newContext(); + public static void initTests() { + initSandbox(); } /** * Removes test databases and closes the database context. */ @AfterClass - public static void closeContext() { - context.close(); - assertTrue("Sandbox could not be deleted.", sandbox().delete()); - } - - /** - * Creates a new specified context. - * @return context - */ - public static Context newContext() { - final IOFile sb = sandbox(); - Options.setSystem(StaticOptions.DBPATH.name(), sb.path() + "/data"); - Options.setSystem(StaticOptions.WEBPATH.name(), sb.path() + "/webapp"); - Options.setSystem(StaticOptions.RESTXQPATH.name(), sb.path() + "/webapp"); - Options.setSystem(StaticOptions.REPOPATH.name(), sb.path() + "/repo"); - try { - return new Context(); - } finally { - Options.setSystem(StaticOptions.DBPATH.name(), ""); - Options.setSystem(StaticOptions.WEBPATH.name(), ""); - Options.setSystem(StaticOptions.RESTXQPATH.name(), ""); - Options.setSystem(StaticOptions.REPOPATH.name(), ""); - } - } - - /** - * Creates a new, sandboxed server instance. - * @param args additional arguments - * @return server instance - * @throws IOException I/O exception - */ - public static BaseXServer createServer(final String... args) throws IOException { - try { - System.setOut(NULL); - final StringList sl = new StringList("-z", "-p" + DB_PORT, "-e" + EVENT_PORT, "-q"); - for(final String arg : args) sl.add(arg); - final BaseXServer server = new BaseXServer(sl.finish()); - server.context.soptions.set(StaticOptions.DBPATH, sandbox().path()); - return server; - } finally { - System.setOut(OUT); - } - } - - /** - * Stops a server instance. - * @param server server - * @throws IOException I/O exception - */ - public static void stopServer(final BaseXServer server) throws IOException { - try { - System.setOut(NULL); - if(server != null) server.stop(); - } finally { - System.setOut(OUT); - } - } - - /** - * Creates a client instance. - * @param login optional login data - * @return client instance - * @throws IOException I/O exception - */ - public static ClientSession createClient(final String... login) throws IOException { - final String user = login.length > 0 ? login[0] : UserText.ADMIN; - final String pass = login.length > 1 ? login[1] : UserText.ADMIN; - return new ClientSession(S_LOCALHOST, DB_PORT, user, pass); - } - - /** - * Returns the sandbox database path. - * @return database path - */ - public static IOFile sandbox() { - return new IOFile(Prop.TMP, NAME); - } - - /** - * Normalizes newlines in a query result. - * @param result input string - * @return normalized string - */ - public static String normNL(final Object result) { - return result.toString().replaceAll("(\r?\n|\r) *", "\n"); - } - - /** Client. */ - public static final class Client extends Thread { - /** Start signal. */ - private final CountDownLatch startSignal; - /** Stop signal. */ - private final CountDownLatch stopSignal; - /** Client session. */ - private final ClientSession session; - /** Command string. */ - private final Command cmd; - /** Fail flag. */ - public String error; - - /** - * Client constructor. - * @param c command string to execute - * @param start start signal - * @param stop stop signal - * @throws IOException I/O exception while establishing the session - */ - public Client(final Command c, final CountDownLatch start, final CountDownLatch stop) - throws IOException { - - session = createClient(); - cmd = c; - startSignal = start; - stopSignal = stop; - start(); - } - - @Override - public void run() { - try { - if(startSignal != null) startSignal.await(); - session.execute(cmd); - session.close(); - } catch(final Throwable ex) { - error = "\n" + cmd + '\n' + ex; - } finally { - if(stopSignal != null) stopSignal.countDown(); - } - } + public static void finishTests() { + finishSandbox(); } } diff -Nru basex-8.1.1/basex-api/src/test/java/org/expath/ns/GeoTest.java basex-8.2.3/basex-api/src/test/java/org/expath/ns/GeoTest.java --- basex-8.1.1/basex-api/src/test/java/org/expath/ns/GeoTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-api/src/test/java/org/expath/ns/GeoTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -877,13 +877,18 @@ final String q = "import module namespace geo='http://expath.org/ns/geo'; " + "declare namespace gml='http://www.opengis.net/gml';" + query; + System.setErr(NULL); try(final QueryProcessor qp = new QueryProcessor(q, context)) { - final String res = qp.execute().toString().replaceAll("(\\r|\\n) *", ""); + final String res = qp.value().serialize().toString().replaceAll("(\\r|\\n) *", ""); fail("Query did not fail:\n" + query + "\n[E] " + error + "...\n[F] " + res); } catch(final QueryException ex) { if(!ex.qname().eq(error)) fail("Wrong error code:\n[E] " + error + "\n[F] " + ex.qname()); + } catch(final Exception ex) { + fail(ex.toString()); + } finally { + System.setErr(ERR); } } } diff -Nru basex-8.1.1/basex-core/pom.xml basex-8.2.3/basex-core/pom.xml --- basex-8.1.1/basex-core/pom.xml 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/pom.xml 2015-07-14 10:54:40.000000000 +0000 @@ -7,7 +7,7 @@ org.basex basex-parent - 8.1.1 + 8.2.3 .. @@ -43,6 +43,11 @@ jline true + + com.thaiopensource + jing + true + diff -Nru basex-8.1.1/basex-core/.settings/checkstyle.xml basex-8.2.3/basex-core/.settings/checkstyle.xml --- basex-8.1.1/basex-core/.settings/checkstyle.xml 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/.settings/checkstyle.xml 2015-07-14 10:54:40.000000000 +0000 @@ -1,12 +1,12 @@ - - @@ -26,7 +26,7 @@ - + @@ -50,7 +50,6 @@ - diff -Nru basex-8.1.1/basex-core/.settings/dict basex-8.2.3/basex-core/.settings/dict --- basex-8.1.1/basex-core/.settings/dict 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/.settings/dict 2015-07-14 10:54:40.000000000 +0000 @@ -277,9 +277,6 @@ payload credentials xslt -unwatches -unwatching -unwatch username aaron donovan @@ -350,3 +347,7 @@ inlineable autoflush soundex +resizable +updatable +uris +dist diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/api/client/ClientSession.java basex-8.2.3/basex-core/src/main/java/org/basex/api/client/ClientSession.java --- basex-8.1.1/basex-core/src/main/java/org/basex/api/client/ClientSession.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/api/client/ClientSession.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,7 +2,6 @@ import java.io.*; import java.net.*; -import java.util.*; import org.basex.core.*; import org.basex.core.parse.Commands.Cmd; @@ -30,9 +29,6 @@ * @author Christian Gruen */ public class ClientSession extends Session { - /** Event notifications. */ - private final Map notifiers = - Collections.synchronizedMap(new HashMap()); /** Server output (buffered). */ final PrintOutput sout; /** Server input. */ @@ -40,10 +36,6 @@ /** Socket reference. */ private final Socket socket; - /** Socket event host. */ - private final String ehost; - /** Socket event reference. */ - private Socket esocket; /** * Constructor, specifying login data. @@ -100,7 +92,6 @@ final String password, final OutputStream output) throws IOException { super(output); - ehost = host; socket = new Socket(); try { // limit timeout to five seconds @@ -161,7 +152,6 @@ @Override public synchronized void close() throws IOException { - if(esocket != null) esocket.close(); socket.close(); } @@ -178,70 +168,6 @@ } /** - * Watches an event. - * @param name event name - * @param notifier event notification - * @throws IOException I/O exception - */ - public void watch(final String name, final EventNotifier notifier) throws IOException { - sout.write(ServerCmd.WATCH.code); - if(esocket == null) { - sout.flush(); - final BufferInput bi = new BufferInput(sin); - final int eport = Integer.parseInt(bi.readString()); - // initialize event socket - esocket = new Socket(); - esocket.connect(new InetSocketAddress(ehost, eport), 5000); - final OutputStream so = esocket.getOutputStream(); - so.write(bi.readBytes()); - so.write(0); - so.flush(); - final InputStream is = esocket.getInputStream(); - is.read(); - listen(is); - } - send(name); - sout.flush(); - receive(null); - notifiers.put(name, notifier); - } - - /** - * Unwatches an event. - * @param name event name - * @throws IOException I/O exception - */ - public void unwatch(final String name) throws IOException { - sout.write(ServerCmd.UNWATCH.code); - send(name); - sout.flush(); - receive(null); - notifiers.remove(name); - } - - /** - * Starts the listener thread. - * @param input input stream - */ - private void listen(final InputStream input) { - final BufferInput bi = new BufferInput(input); - new Thread() { - @Override - public void run() { - try { - while(true) { - final EventNotifier n = notifiers.get(bi.readString()); - final String l = bi.readString(); - if(n != null) n.notify(l); - } - } catch(final IOException ex) { - // listener did not receive any more input - } - } - }.start(); - } - - /** * Sends the specified stream to the server. * @param input input stream * @throws IOException I/O exception @@ -334,9 +260,4 @@ if(!ok(bi)) throw new BaseXException(bi.readString()); return o.toString(); } - - @Override - public String toString() { - return ehost + ':' + socket.getPort(); - } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/api/dom/BXElem.java basex-8.2.3/basex-core/src/main/java/org/basex/api/dom/BXElem.java --- basex-8.1.1/basex-core/src/main/java/org/basex/api/dom/BXElem.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/api/dom/BXElem.java 2015-07-14 10:54:40.000000000 +0000 @@ -148,9 +148,9 @@ * @return node or {@code null} */ private ANode attribute(final String name) { - final AxisIter ai = nd.attributes(); + final BasicNodeIter iter = nd.attributes(); final byte[] nm = Token.token(name); - for(ANode n; (n = ai.next()) != null;) if(Token.eq(nm, n.name())) return n.finish(); + for(ANode n; (n = iter.next()) != null;) if(Token.eq(nm, n.name())) return n.finish(); return null; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/api/dom/BXNode.java basex-8.2.3/basex-core/src/main/java/org/basex/api/dom/BXNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/api/dom/BXNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/api/dom/BXNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -268,9 +268,9 @@ */ final BXNList getElements(final String name) { final ANodeList nb = new ANodeList(); - final AxisIter ai = nd.descendant(); + final BasicNodeIter iter = nd.descendant(); final byte[] nm = "*".equals(name) ? null : token(name); - for(ANode n; (n = ai.next()) != null;) { + for(ANode n; (n = iter.next()) != null;) { if(n.type == NodeType.ELM && (nm == null || eq(nm, n.name()))) nb.add(n.finish()); } return new BXNList(nb); @@ -281,7 +281,7 @@ * @param iter axis iterator * @return node cache */ - static ANodeList finish(final AxisIter iter) { + static ANodeList finish(final BasicNodeIter iter) { final ANodeList nl = new ANodeList(); for(ANode n; (n = iter.next()) != null;) nl.add(n.finish()); return nl; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/BaseXClient.java basex-8.2.3/basex-core/src/main/java/org/basex/BaseXClient.java --- basex-8.1.1/basex-core/src/main/java/org/basex/BaseXClient.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/BaseXClient.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,6 +3,7 @@ import static org.basex.core.Text.*; import java.io.*; +import java.net.*; import org.basex.api.client.*; import org.basex.core.*; @@ -57,6 +58,13 @@ Util.out(PASSWORD + COLS); pass = Util.password(); } - return new ClientSession(context, user, pass, out); + + final String host = context.soptions.get(StaticOptions.HOST); + final int port = context.soptions.get(StaticOptions.PORT); + try { + return new ClientSession(host, port, user, pass, out); + } catch(final ConnectException ex) { + throw new BaseXException(CONNECTION_ERROR_X, port); + } } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/BaseXGUI.java basex-8.2.3/basex-core/src/main/java/org/basex/BaseXGUI.java --- basex-8.1.1/basex-core/src/main/java/org/basex/BaseXGUI.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/BaseXGUI.java 2015-07-14 10:54:40.000000000 +0000 @@ -63,10 +63,6 @@ // read options final GUIOptions gopts = new GUIOptions(); - // cache results to pass them on to all visualizations - context.options.set(MainOptions.CACHEQUERY, true); - // reduce number of results to save memory - context.options.set(MainOptions.MAXHITS, gopts.get(GUIOptions.MAXHITS)); // initialize look and feel init(gopts); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/BaseX.java basex-8.2.3/basex-core/src/main/java/org/basex/BaseX.java --- basex-8.1.1/basex-core/src/main/java/org/basex/BaseX.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/BaseX.java 2015-07-14 10:54:40.000000000 +0000 @@ -86,9 +86,6 @@ execute(val); execute(new Set(MainOptions.QUERYPATH, ""), false); console = false; - } else if(c == 'd') { - // toggle debug mode - Prop.debug ^= true; } else if(c == 'D') { // hidden option: show/hide dot query graph execute(new Set(MainOptions.DOTPLAN, null), false); @@ -232,8 +229,11 @@ String v = null; if(arg.dash()) { c = arg.next(); - if(c == 'b' || c == 'c' || c == 'C' || c == 'i' || c == 'I' || c == 'o' || c == 'q' || - c == 'r' || c == 's' || c == 't' && local()) { + if(c == 'd') { + // activate debug mode + Prop.debug = true; + } else if(c == 'b' || c == 'c' || c == 'C' || c == 'i' || c == 'I' || c == 'o' || + c == 'q' || c == 'r' || c == 's' || c == 't' && local()) { // options followed by a string v = arg.string(); } else if(c == 'd' || c == 'D' && local() || c == 'u' && local() || c == 'R' || diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/BaseXServer.java basex-8.2.3/basex-core/src/main/java/org/basex/BaseXServer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/BaseXServer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/BaseXServer.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,7 +9,6 @@ import org.basex.api.client.*; import org.basex.core.*; import org.basex.io.*; -import org.basex.io.in.*; import org.basex.server.*; import org.basex.server.Log.LogType; import org.basex.util.*; @@ -30,10 +29,6 @@ private volatile boolean running; /** Indicates if server is to be stopped. */ private volatile boolean stop; - /** Event server socket. */ - private ServerSocket esocket; - /** Events listener. */ - private EventListener events; /** Initial commands. */ private StringList commands; /** Server socket. */ @@ -79,9 +74,6 @@ final StaticOptions sopts = context.soptions; final int port = sopts.get(StaticOptions.SERVERPORT); - final int eport = sopts.get(StaticOptions.EVENTPORT); - // ensure that port numbers are different - if(port == eport) throw new BaseXException(PORT_TWICE_X, port); final String host = sopts.get(StaticOptions.SERVERHOST); final InetAddress addr = host.isEmpty() ? null : InetAddress.getByName(host); @@ -94,7 +86,7 @@ } if(stop) { - stop(port, eport); + stop(port); if(!quiet) Util.outln(SRV_STOPPED_PORT_X, port); Performance.sleep(1000); return; @@ -107,13 +99,10 @@ socket = new ServerSocket(); socket.setReuseAddress(true); socket.bind(new InetSocketAddress(addr, port)); - esocket = new ServerSocket(); - esocket.setReuseAddress(true); - esocket.bind(new InetSocketAddress(addr, eport)); stopFile = stopFile(port); } catch(final IOException ex) { context.log.writeServer(LogType.ERROR, Util.message(ex)); - throw ex; + throw ex instanceof BindException ? new IOException(Util.info(SRV_RUNNING_X, port)) : ex; } new Thread(this).start(); @@ -204,7 +193,6 @@ try { // close interactive input if server was stopped by another process - esocket.close(); socket.close(); } catch(final IOException ex) { Util.errln(ex); @@ -230,9 +218,6 @@ case 'D': // hidden flag: daemon mode daemon = true; break; - case 'e': // parse event port - context.soptions.set(StaticOptions.EVENTPORT, arg.number()); - break; case 'n': // parse host the server is bound to context.soptions.set(StaticOptions.SERVERHOST, arg.string()); break; @@ -266,8 +251,7 @@ * @throws IOException I/O exception */ public void stop() throws IOException { - final StaticOptions sopts = context.soptions; - stop(sopts.get(StaticOptions.SERVERPORT), sopts.get(StaticOptions.EVENTPORT)); + stop(context.soptions.get(StaticOptions.SERVERPORT)); } // STATIC METHODS =========================================================== @@ -280,16 +264,17 @@ */ public static void start(final int port, final String... args) throws BaseXException { // check if server is already running (needs some time) - if(ping(S_LOCALHOST, port)) throw new BaseXException(SRV_RUNNING); + final String host = S_LOCALHOST; + if(ping(host, port)) throw new BaseXException(SRV_RUNNING_X, port); Util.start(BaseXServer.class, args); // try to connect to the new server instance for(int c = 1; c < 10; ++c) { Performance.sleep(c * 100L); - if(ping(S_LOCALHOST, port)) return; + if(ping(host, port)) return; } - throw new BaseXException(CONNECTION_ERROR); + throw new BaseXException(CONNECTION_ERROR_X, port); } /** @@ -314,20 +299,19 @@ /** * Stops the server. * @param port server port - * @param eport event port * @throws IOException I/O exception */ - public static void stop(final int port, final int eport) throws IOException { + public static void stop(final int port) throws IOException { final IOFile stop = stopFile(port); try { stop.touch(); - new Socket(S_LOCALHOST, eport).close(); new Socket(S_LOCALHOST, port).close(); // wait and check if server was really stopped do Performance.sleep(100); while(ping(S_LOCALHOST, port)); - } catch(final IOException ex) { + } catch(final ConnectException ex) { + throw new IOException(Util.info(CONNECTION_ERROR_X, port)); + } finally { stop.delete(); - throw ex; } } @@ -342,47 +326,6 @@ } } - /** - * Initializes the event listener. - */ - public void initEvents() { - if(events == null) { - events = new EventListener(); - events.start(); - } - } - - /** - * Inner class to listen for event registrations. - * - * @author BaseX Team 2005-15, BSD License - * @author Andreas Weiler - */ - private final class EventListener extends Thread { - @Override - public void run() { - while(running) { - try { - final Socket es = esocket.accept(); - if(stopFile.exists()) { - esocket.close(); - break; - } - final BufferInput bi = new BufferInput(es.getInputStream()); - final long id = Strings.toLong(bi.readString()); - for(final ClientListener s : context.sessions) { - if(s.getId() == id) { - s.register(es); - break; - } - } - } catch(final IOException ex) { - break; - } - } - } - } - @Override public String header() { return Util.info(S_CONSOLE, S_SERVER); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/build/Builder.java basex-8.2.3/basex-core/src/main/java/org/basex/build/Builder.java --- basex-8.1.1/basex-core/src/main/java/org/basex/build/Builder.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/build/Builder.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,7 +8,6 @@ import org.basex.core.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.index.name.*; import org.basex.index.path.*; import org.basex.io.*; @@ -28,7 +27,7 @@ /** Tree structure. */ final PathSummary path = new PathSummary(); /** Namespace index. */ - final Namespaces ns = new Namespaces(); + final Namespaces nspaces = new Namespaces(); /** Parser instance. */ final Parser parser; /** Database name. */ @@ -47,9 +46,9 @@ Names attrNames; /** Parent stack. */ - private final IntList pstack = new IntList(); + private final IntList parStack = new IntList(); /** Stack with element names. */ - private final IntList tstack = new IntList(); + private final IntList elemStack = new IntList(); /** Current tree height. */ private int level; @@ -82,9 +81,9 @@ */ public final void openDoc(final byte[] value) throws IOException { path.put(0, Data.DOC, level); - pstack.set(level++, meta.size); + parStack.set(level++, meta.size); addDoc(value); - ns.prepare(); + nspaces.open(); } /** @@ -92,10 +91,10 @@ * @throws IOException I/O exception */ public final void closeDoc() throws IOException { - final int pre = pstack.get(--level); + final int pre = parStack.get(--level); setSize(pre, meta.size - pre); ++meta.ndocs; - ns.close(meta.size); + nspaces.close(meta.size); } /** @@ -120,8 +119,8 @@ public final void emptyElem(final byte[] name, final Atts att, final Atts nsp) throws IOException { addElem(name, att, nsp); - final int pre = pstack.get(level); - ns.close(pre); + final int pre = parStack.get(level); + nspaces.close(pre); if(att.size() > IO.MAXATTS) setSize(pre, meta.size - pre); } @@ -132,9 +131,9 @@ public final void closeElem() throws IOException { checkStop(); --level; - final int pre = pstack.get(level); + final int pre = parStack.get(level); setSize(pre, meta.size - pre); - ns.close(pre); + nspaces.close(pre); } /** @@ -223,24 +222,24 @@ * size value; if this node has further descendants, {@link #setSize} must * be called to set the final size value. * @param dist distance to parent - * @param name element name + * @param nameId id of element name * @param asize number of attributes - * @param uri namespace uri reference - * @param ne namespace flag + * @param uriId id of namespace uri + * @param ne namespace flag (indicates if this element introduces new namespaces) * @throws IOException I/O exception */ - protected abstract void addElem(int dist, int name, int asize, int uri, boolean ne) + protected abstract void addElem(int dist, int nameId, int asize, int uriId, boolean ne) throws IOException; /** * Adds an attribute to the database. - * @param name attribute name + * @param nameId id of attribute name * @param value attribute value * @param dist distance to parent - * @param uri namespace uri reference + * @param uriId id of namespace uri * @throws IOException I/O exception */ - protected abstract void addAttr(int name, byte[] value, int dist, int uri) throws IOException; + protected abstract void addAttr(int nameId, byte[] value, int dist, int uriId) throws IOException; /** * Adds a text node to the database. @@ -264,55 +263,53 @@ /** * Adds an element node to the storage. * @param name element name - * @param att attributes + * @param atts attributes * @param nsp namespaces * @throws IOException I/O exception */ - private void addElem(final byte[] name, final Atts att, final Atts nsp) throws IOException { + private void addElem(final byte[] name, final Atts atts, final Atts nsp) throws IOException { // get reference of element name - int n = elemNames.index(name, null, true); - path.put(n, Data.ELEM, level); + int nameId = elemNames.index(name, null, true); + path.put(nameId, Data.ELEM, level); // cache pre value final int pre = meta.size; // remember id of element name and parent reference - tstack.set(level, n); - pstack.set(level, pre); + elemStack.set(level, nameId); + parStack.set(level, pre); // parse namespaces - ns.prepare(); - final int nl = nsp.size(); - for(int nx = 0; nx < nl; nx++) ns.add(nsp.name(nx), nsp.value(nx), pre); + nspaces.open(pre, nsp); // get and store element references - final int dis = level == 0 ? 1 : pre - pstack.get(level - 1); - final int as = att.size(); - int u = ns.uri(name, true); - if(u == 0 && indexOf(name, ':') != -1 && !eq(prefix(name), XML)) + final int dis = level == 0 ? 1 : pre - parStack.get(level - 1); + final int as = atts.size(); + final byte[] pref = prefix(name); + int uriId = nspaces.uriIdForPrefix(pref, true); + if(uriId == 0 && pref.length != 0 && !eq(pref, XML)) throw new BuildException(WHICHNS, parser.detail(), prefix(name)); - addElem(dis, n, Math.min(IO.MAXATTS, as + 1), u, nl != 0); + addElem(dis, nameId, Math.min(IO.MAXATTS, as + 1), uriId, !nsp.isEmpty()); // get and store attribute references for(int a = 0; a < as; ++a) { - final byte[] av = att.value(a); - final byte[] an = att.name(a); - n = attrNames.index(an, av, true); - u = ns.uri(an, false); - if(u == 0 && indexOf(an, ':') != -1 && !eq(prefix(an), XML)) + final byte[] an = atts.name(a), av = atts.value(a); + final byte[] ap = prefix(an); + nameId = attrNames.index(an, av, true); + uriId = nspaces.uriIdForPrefix(ap, false); + if(uriId == 0 && ap.length != 0 && !eq(ap, XML)) throw new BuildException(WHICHNS, parser.detail(), an); - path.put(n, Data.ATTR, level + 1, av, meta); - addAttr(n, av, Math.min(IO.MAXATTS, a + 1), u); + path.put(nameId, Data.ATTR, level + 1, av, meta); + addAttr(nameId, av, Math.min(IO.MAXATTS, a + 1), uriId); } // set leaf node information in index - if(level > 1) elemNames.stat(tstack.get(level - 1)).setLeaf(false); + if(level > 1) elemNames.stat(elemStack.get(level - 1)).setLeaf(false); - // check if data ranges exceed database limits, - // based on the storage details in {@link Data} + // check if data ranges exceed database limits, based on the storage details in {@link Data} limit(elemNames.size(), 0x8000, LIMITELEMS); limit(attrNames.size(), 0x8000, LIMITATTS); - limit(ns.size(), 0x100, LIMITNS); + limit(nspaces.size(), 0x100, LIMITNS); if(meta.size < 0) limit(0, 0, LIMITRANGE); } @@ -336,7 +333,7 @@ private void addText(final byte[] value, final byte kind) throws IOException { final int l = level; if(l > 1) { - final int i = tstack.get(l - 1); + final int i = elemStack.get(l - 1); // text node processing for statistics if(kind == Data.TEXT) elemNames.index(i, value); // set leaf node information in index @@ -344,6 +341,6 @@ } path.put(0, kind, l, value, meta); - addText(value, l == 0 ? 1 : meta.size - pstack.get(l - 1), kind); + addText(value, l == 0 ? 1 : meta.size - parStack.get(l - 1), kind); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/build/DiskBuilder.java basex-8.2.3/basex-core/src/main/java/org/basex/build/DiskBuilder.java --- basex-8.1.1/basex-core/src/main/java/org/basex/build/DiskBuilder.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/build/DiskBuilder.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,7 +8,6 @@ import org.basex.core.*; import org.basex.core.cmd.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.index.name.*; import org.basex.io.*; import org.basex.io.in.DataInput; @@ -101,7 +100,7 @@ meta.dbfile(DATATMP).delete(); // return database instance - return new DiskData(meta, elemNames, attrNames, path, ns); + return new DiskData(meta, elemNames, attrNames, path, nspaces); } @Override @@ -144,12 +143,12 @@ } @Override - protected void addElem(final int dist, final int name, final int asize, final int uri, + protected void addElem(final int dist, final int nameId, final int asize, final int uriId, final boolean ne) throws IOException { tout.write1(asize << 3 | Data.ELEM); - tout.write2((ne ? 1 << 15 : 0) | name); - tout.write1(uri); + tout.write2((ne ? 1 << 15 : 0) | nameId); + tout.write1(uriId); tout.write4(dist); tout.write4(asize); tout.write4(meta.size++); @@ -158,13 +157,13 @@ } @Override - protected void addAttr(final int name, final byte[] value, final int dist, final int uri) + protected void addAttr(final int nameId, final byte[] value, final int dist, final int uriId) throws IOException { tout.write1(dist << 3 | Data.ATTR); - tout.write2(name); + tout.write2(nameId); tout.write5(textOff(value, false)); - tout.write4(uri); + tout.write4(uriId); tout.write4(meta.size++); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/build/json/JsonOptions.java basex-8.2.3/basex-core/src/main/java/org/basex/build/json/JsonOptions.java --- basex-8.1.1/basex-core/src/main/java/org/basex/build/json/JsonOptions.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/build/json/JsonOptions.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,13 +11,13 @@ * @author Christian Gruen */ public class JsonOptions extends Options { - /** Option: merge type information. */ + /** Option: merge type information (custom). */ public static final BooleanOption MERGE = new BooleanOption("merge", false); - /** Option: include string types. */ + /** Option: include string types (custom). */ public static final BooleanOption STRINGS = new BooleanOption("strings", false); - /** Option: lax conversion of names to QNames. */ + /** Option: lax conversion of names to QNames (custom). */ public static final BooleanOption LAX = new BooleanOption("lax", false); - /** Option: format. */ + /** Option: format (custom). */ public static final EnumOption FORMAT = new EnumOption<>("format", JsonFormat.DIRECT); /** JSON formats. */ @@ -25,6 +25,7 @@ /** Direct. */ DIRECT, /** Attributes. */ ATTRIBUTES, /** JsonML. */ JSONML, + /** Basic. */ BASIC, /** Map (non-XML). */ MAP; @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/build/json/JsonParserOptions.java basex-8.2.3/basex-core/src/main/java/org/basex/build/json/JsonParserOptions.java --- basex-8.1.1/basex-core/src/main/java/org/basex/build/json/JsonParserOptions.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/build/json/JsonParserOptions.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,23 +11,26 @@ * @author Christian Gruen */ public final class JsonParserOptions extends JsonOptions { - /** Option: encoding. */ - public static final StringOption ENCODING = new StringOption("encoding"); - /** Option: unescape special characters. */ + /** Option: unescape special characters (parse-json, json-to-xml). */ public static final BooleanOption UNESCAPE = new BooleanOption("unescape", true); - /** Option: liberal parsing. */ + /** Option: liberal parsing (parse-json, json-to-xml). */ public static final BooleanOption LIBERAL = new BooleanOption("liberal", false); - /** Option: fallback function. */ + /** Option: fallback function (parse-json, json-to-xml). */ public static final FuncOption FALLBACK = new FuncOption("fallback"); - /** Option: handle duplicates. */ + /** Option: handle duplicates (parse-json, json-to-xml). */ public static final EnumOption DUPLICATES = - new EnumOption<>("duplicates", JsonDuplicates.USE_LAST); + new EnumOption<>("duplicates", JsonDuplicates.class); + /** Option: validation (json-to-xml). */ + public static final BooleanOption VALIDATE = new BooleanOption("validate", false); + /** Option: encoding (custom). */ + public static final StringOption ENCODING = new StringOption("encoding"); /** Duplicate handling. */ public enum JsonDuplicates { /** Reject. */ REJECT, /** Use first. */ USE_FIRST, - /** Use last. */ USE_LAST; + /** Use last. */ USE_LAST, + /** Retain. */ RETAIN; @Override public String toString() { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/build/json/JsonSerialOptions.java basex-8.2.3/basex-core/src/main/java/org/basex/build/json/JsonSerialOptions.java --- basex-8.1.1/basex-core/src/main/java/org/basex/build/json/JsonSerialOptions.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/build/json/JsonSerialOptions.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,6 +9,8 @@ * @author Christian Gruen */ public final class JsonSerialOptions extends JsonOptions { - /** Option: escape special characters. */ + /** Option: merge type information (xml-to-json, custom). */ + public static final BooleanOption INDENT = new BooleanOption("indent", false); + /** Option: escape special characters (custom). */ public static final BooleanOption ESCAPE = new BooleanOption("escape", true); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/build/MemBuilder.java basex-8.2.3/basex-core/src/main/java/org/basex/build/MemBuilder.java --- basex-8.1.1/basex-core/src/main/java/org/basex/build/MemBuilder.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/build/MemBuilder.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,7 +3,6 @@ import java.io.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.io.*; /** @@ -82,7 +81,7 @@ * Initializes the builder. */ public void init() { - data = new MemData(path, ns, parser.options); + data = new MemData(path, nspaces, parser.options); meta = data.meta; meta.name = dbname; @@ -116,15 +115,15 @@ } @Override - protected void addElem(final int dist, final int name, final int asize, final int uri, + protected void addElem(final int dist, final int nameId, final int asize, final int uriId, final boolean ne) { - data.elem(dist, name, asize, asize, uri, ne); + data.elem(dist, nameId, asize, asize, uriId, ne); data.insert(meta.size); } @Override - protected void addAttr(final int name, final byte[] value, final int dist, final int uri) { - data.attr(meta.size, dist, name, value, uri, false); + protected void addAttr(final int nameId, final byte[] value, final int dist, final int uriId) { + data.attr(meta.size, dist, nameId, value, uriId); data.insert(meta.size); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Add.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Add.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Add.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Add.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,8 +9,8 @@ import org.basex.core.parse.*; import org.basex.core.users.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.io.*; +import org.basex.query.up.atomic.*; import org.basex.util.*; /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/AEvent.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/AEvent.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/AEvent.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/AEvent.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -package org.basex.core.cmd; - -import org.basex.core.*; -import org.basex.core.locks.*; -import org.basex.core.users.*; - -/** - * Abstract class for database events. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -abstract class AEvent extends Command { - /** - * Protected constructor. - * @param args arguments - */ - AEvent(final String... args) { - super(Perm.ADMIN, false, args); - } - - @Override - public void databases(final LockResult lr) { - lr.write.add(DBLocking.EVENT); // Event operations are exclusive - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/AQuery.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/AQuery.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/AQuery.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/AQuery.java 2015-07-14 10:54:40.000000000 +0000 @@ -10,13 +10,13 @@ import org.basex.core.locks.*; import org.basex.core.parse.*; import org.basex.core.users.*; -import org.basex.data.*; import org.basex.io.*; import org.basex.io.out.*; import org.basex.io.serial.*; import org.basex.io.serial.dot.*; import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.util.*; @@ -37,7 +37,7 @@ private QueryInfo info; /** Query result. */ - private Result result; + private Value result; /** * Protected constructor. @@ -57,8 +57,8 @@ final boolean query(final String query) { final Performance p = new Performance(); String error; - if(cause != null) { - error = Util.message(cause); + if(exception != null) { + error = Util.message(exception); } else { try { long hits = 0; @@ -79,8 +79,8 @@ final PrintOutput po = r == 0 && serial ? out : new NullOutput(); try(final Serializer ser = qp.getSerializer(po)) { - if(options.get(MainOptions.CACHEQUERY)) { - result = qp.execute(); + if(maxResults >= 0) { + result = qp.cache(maxResults); info.evaluating += p.time(); result.serialize(ser); hits = result.size(); @@ -108,7 +108,7 @@ return info(info.toString(qp, out.size(), hits, options.get(MainOptions.QUERYINFO))); } catch(final QueryException | IOException ex) { - cause = ex; + exception = ex; error = Util.message(ex); } catch(final ProcException ex) { error = INTERRUPTED; @@ -157,38 +157,19 @@ return qp.updating; } catch(final QueryException ex) { Util.debug(ex); - cause = ex; + exception = ex; qp.close(); return false; } } /** - * Evaluates the query and returns the result as {@link DBNodes} instance. - * @return result or {@code null} if result cannot be represented as {@link DBNodes} instance. - */ - final DBNodes dbNodes() { - try { - final Result res = qp(args[0], context).execute(); - if(res instanceof DBNodes) return (DBNodes) res; - // return empty result set - if(res.size() == 0) return new DBNodes(context.data()); - } catch(final QueryException ex) { - error(Util.message(ex)); - } finally { - qp.close(); - qp = null; - } - return null; - } - - /** * Returns a query processor instance. * @param query query string * @param ctx database context * @return query processor */ - private QueryProcessor qp(final String query, final Context ctx) { + protected QueryProcessor qp(final String query, final Context ctx) { if(qp == null) { qp = proc(new QueryProcessor(query, ctx)); if(info == null) info = qp.qc.info; @@ -197,6 +178,16 @@ } /** + * Closes the query processor. + */ + protected void closeQp() { + if(qp != null) { + qp.close(); + qp = null; + } + } + + /** * Returns the serialization parameters. * @param ctx context * @return serialization parameters @@ -322,8 +313,8 @@ } @Override - public final Result finish() { - final Result r = result; + public final Value finish() { + final Value r = result; result = null; return r; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/AUser.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/AUser.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/AUser.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/AUser.java 2015-07-14 10:54:40.000000000 +0000 @@ -38,10 +38,10 @@ */ boolean run(final int off, final boolean opt) { final String name = args[off]; - final String pattern = off + 1 < args.length ? args[off + 1] : null; + final String pattern = off + 1 < args.length ? args[off + 1] : ""; if(!Databases.validName(name, true)) return error(NAME_INVALID_X, name); - if(pattern != null && !Databases.validName(pattern, true)) + if(!pattern.isEmpty() && !Databases.validName(pattern, true)) return error(NAME_INVALID_X, pattern); // retrieve all users; stop if no user is found diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/CreateEvent.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/CreateEvent.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/CreateEvent.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/CreateEvent.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -package org.basex.core.cmd; - -import static org.basex.core.Text.*; - -import org.basex.core.parse.*; -import org.basex.core.parse.Commands.Cmd; -import org.basex.core.parse.Commands.CmdCreate; - -/** - * Evaluates the 'create event' command and creates a new event. - * - * @author BaseX Team 2005-15, BSD License - * @author Roman Raedle - * @author Andreas Weiler - */ -public final class CreateEvent extends AEvent { - /** - * Default constructor. - * @param name user name - */ - public CreateEvent(final String name) { - super(name); - } - - @Override - protected boolean run() { - final String name = args[0]; - return context.events.create(name) ? info(EVENT_CREATED_X, name) : error(EVENT_EXISTS_X, name); - } - - @Override - public void build(final CmdBuilder cb) { - cb.init(Cmd.CREATE + " " + CmdCreate.EVENT).args(); - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Delete.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Delete.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Delete.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Delete.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,8 +4,8 @@ import org.basex.core.users.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.io.*; +import org.basex.query.up.atomic.*; import org.basex.util.list.*; /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/DropEvent.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/DropEvent.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/DropEvent.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/DropEvent.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,36 +0,0 @@ -package org.basex.core.cmd; - -import static org.basex.core.Text.*; - -import org.basex.core.parse.*; -import org.basex.core.parse.Commands.Cmd; -import org.basex.core.parse.Commands.CmdDrop; - -/** - * Evaluates the 'drop event' command and drops an existing event. - * - * @author BaseX Team 2005-15, BSD License - * @author Roman Raedle - * @author Andreas Weiler - */ -public final class DropEvent extends AEvent { - /** - * Default constructor. - * @param name user name - */ - public DropEvent(final String name) { - super(name); - } - - @Override - protected boolean run() { - final String name = args[0]; - return context.events.drop(name) ? - info(EVENT_DROPPED_X, name) : error(EVENT_UNKNOWN_X, name); - } - - @Override - public void build(final CmdBuilder cb) { - cb.init(Cmd.DROP + " " + CmdDrop.EVENT).args(); - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/DropUser.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/DropUser.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/DropUser.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/DropUser.java 2015-07-14 10:54:40.000000000 +0000 @@ -26,10 +26,10 @@ /** * Constructor for dropping local database users. * @param name name of user - * @param db database (may be {@code null}) + * @param pattern database pattern (may be {@code null}) */ - public DropUser(final String name, final String db) { - super(name, db); + public DropUser(final String name, final String pattern) { + super(name, pattern == null ? "" : pattern); } @Override @@ -46,13 +46,13 @@ final Users users = context.users; final User user = users.get(name); if(user != null) { - if(pattern == null) { + if(pattern.isEmpty()) { for(final ClientListener s : context.sessions) { if(s.context().user().name().equals(name)) return !info(USER_LOGGED_IN_X, name); } } users.drop(users.get(name), pattern); - return info(pattern == null ? USER_DROPPED_X : USER_DROPPED_X_X, name, pattern); + return info(pattern.isEmpty() ? USER_DROPPED_X : USER_DROPPED_X_X, name, pattern); } return true; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Execute.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Execute.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Execute.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Execute.java 2015-07-14 10:54:40.000000000 +0000 @@ -54,7 +54,7 @@ proc(null); sb.append(c.info()); if(!ok) { - cause = c.cause; + exception = c.exception(); return error(sb.toString()); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Get.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Get.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Get.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Get.java 2015-07-14 10:54:40.000000000 +0000 @@ -33,15 +33,15 @@ /** * Default constructor. - * @param key key to be found + * @param key key to be found (can be {@code null}) */ public Get(final String key) { - super(key); + super(key == null ? "" : key); } @Override protected boolean run() throws IOException { - if(args[0] == null) { + if(args[0].isEmpty()) { // retrieve values of all options if(context.user().has(Perm.ADMIN)) { out.println(GLOBAL_OPTIONS + COL); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Grant.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Grant.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Grant.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Grant.java 2015-07-14 10:54:40.000000000 +0000 @@ -29,25 +29,26 @@ * Constructor, specifying a database. * @param permission permission * @param user user name - * @param pattern database pattern + * @param pattern database pattern (may be {@code null}) */ public Grant(final Object permission, final String user, final String pattern) { - super(permission.toString(), user, pattern); + super(permission.toString(), user, pattern == null ? "" : pattern); } @Override protected boolean run() { // find permission final CmdPerm cmd = getOption(CmdPerm.class); + final boolean local = args[2].isEmpty(); if(cmd == CmdPerm.NONE) { prm = Perm.NONE; } else if(cmd == CmdPerm.READ) { prm = Perm.READ; } else if(cmd == CmdPerm.WRITE) { prm = Perm.WRITE; - } else if(cmd == CmdPerm.CREATE && args[2] == null) { + } else if(cmd == CmdPerm.CREATE && local) { prm = Perm.CREATE; - } else if(cmd == CmdPerm.ADMIN && args[2] == null) { + } else if(cmd == CmdPerm.ADMIN && local) { prm = Perm.ADMIN; } if(prm == null) return error(PERM_UNKNOWN_X, args[0]); @@ -63,7 +64,7 @@ final Users users = context.users; final User user = users.get(name); Users.perm(user, prm, pattern); - return info(pattern == null ? GRANTED_X_X : GRANTED_ON_X_X_X, args[0], name, pattern); + return info(pattern.isEmpty() ? GRANTED_X_X : GRANTED_ON_X_X_X, args[0], name, pattern); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Help.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Help.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Help.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Help.java 2015-07-14 10:54:40.000000000 +0000 @@ -18,16 +18,16 @@ public final class Help extends Command { /** * Default constructor. - * @param arg optional argument + * @param arg argument (can be {@code null}) */ public Help(final String arg) { - super(Perm.NONE, arg); + super(Perm.NONE, arg == null ? "" : arg); } @Override protected boolean run() throws IOException { final String key = args[0]; - if(key != null && !key.isEmpty()) { + if(!key.isEmpty()) { final Cmd cmd = getOption(key, Cmd.class); if(cmd == null) return error(UNKNOWN_CMD_X, this); out.println(cmd.help(true)); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/InfoDB.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/InfoDB.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/InfoDB.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/InfoDB.java 2015-07-14 10:54:40.000000000 +0000 @@ -63,13 +63,13 @@ // count number of raw files info(tb, DOCUMENTS, meta.ndocs); info(tb, BINARIES, meta.path != null ? meta.binaries().descendants().size() : 0); - info(tb, TIMESTAMP, DateTime.format(new Date(meta.dbtime()), DateTime.DATETIME)); + info(tb, TIMESTAMP, DateTime.format(new Date(meta.dbtime()))); if(meta.corrupt) tb.add(' ' + DB_CORRUPT + NL); tb.add(NL).addExt(header, RES_PROPS); if(create && !meta.original.isEmpty()) info(tb, INPUT_PATH, meta.original); if(meta.filesize != 0) info(tb, INPUT_SIZE, Performance.format(meta.filesize)); - info(tb, TIMESTAMP, DateTime.format(new Date(meta.time), DateTime.DATETIME)); + info(tb, TIMESTAMP, DateTime.format(new Date(meta.time))); info(tb, ENCODING, meta.encoding); info(tb, MainOptions.CHOP.name(), meta.chop); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/InfoIndex.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/InfoIndex.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/InfoIndex.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/InfoIndex.java 2015-07-14 10:54:40.000000000 +0000 @@ -34,13 +34,13 @@ * @param type optional index type, defined in {@link CmdIndexInfo} */ public InfoIndex(final Object type) { - super(true, type != null && type != CmdIndexInfo.NULL ? type.toString() : null); + super(true, type != null && type != CmdIndexInfo.NULL ? type.toString() : ""); } @Override protected boolean run() throws IOException { final Data data = context.data(); - if(args[0] != null) { + if(!args[0].isEmpty()) { final CmdIndexInfo ci = getOption(CmdIndexInfo.class); if(ci == null) return error(UNKNOWN_CMD_X, this); final byte[] inf = info(ci, data, options); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Info.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Info.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Info.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Info.java 2015-07-14 10:54:40.000000000 +0000 @@ -46,10 +46,8 @@ info(tb, VERSINFO, Prop.VERSION); final User user = context.user(); - if(user.has(Perm.CREATE)) { - Performance.gc(1); - info(tb, USED_MEM, Performance.getMemory()); - } + info(tb, USED_MEM, Performance.getMemory()); + if(user.has(Perm.ADMIN)) { final StaticOptions sopts = context.soptions; tb.add(NL + GLOBAL_OPTIONS + COL + NL); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/InfoStorage.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/InfoStorage.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/InfoStorage.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/InfoStorage.java 2015-07-14 10:54:40.000000000 +0000 @@ -12,6 +12,9 @@ import org.basex.core.parse.Commands.CmdInfo; import org.basex.core.users.*; import org.basex.data.*; +import org.basex.query.*; +import org.basex.query.value.*; +import org.basex.query.value.seq.*; import org.basex.util.*; import org.basex.util.list.*; @@ -25,11 +28,11 @@ public final class InfoStorage extends AQuery { /** * Default constructor. - * @param arg optional arguments + * @param arg arguments (can be {@code null}) */ public InfoStorage(final String... arg) { - super(Perm.READ, true, arg.length > 0 && arg[0] != null && !arg[0].isEmpty() ? - arg[0] : null, arg.length > 1 ? arg[1] : null); + super(Perm.READ, true, arg.length > 0 && arg[0] != null ? arg[0] : "", + arg.length > 1 && arg[1] != null ? arg[1] : ""); } @Override @@ -38,19 +41,30 @@ final String start = args[0]; final String end = args[1]; - // evaluate input as number range or xquery - final DBNodes nodes = start != null && toInt(start) == Integer.MIN_VALUE ? dbNodes() : null; + DBNodes nodes = null; + if(!start.isEmpty() && toInt(start) == Integer.MIN_VALUE) { + try { + // evaluate input as query + final Value value = qp(args[0], context).value(); + if(value instanceof DBNodes) nodes = (DBNodes) value; + } catch(final QueryException ex) { + error(Util.message(ex)); + } finally { + closeQp(); + } + } + final Data data = context.data(); if(nodes != null) { final Table table = th(); - for(final int n : nodes.pres) table(table, data, n); + for(final int pre : nodes.pres()) table(table, data, pre); out.print(table.finish()); } else { int ps = 0; int pe = 1000; - if(start != null) { - if(end != null) { + if(!start.isEmpty()) { + if(!end.isEmpty()) { ps = toInt(start); pe = toInt(end) + 1; } else { @@ -120,9 +134,9 @@ tl.add(data.size(pre, k)); tl.add(data.attSize(pre, k)); tl.add(data.id(pre)); - final int u = data.uri(pre, k); - if(data.nsFlag(pre)) tl.add("+" + u); - else tl.add(u); + final int uriId = data.uriId(pre, k); + if(data.nsFlag(pre)) tl.add("+" + uriId); + else tl.add(uriId); tl.add(TABLEKINDS[k]); final byte[] cont; @@ -141,7 +155,7 @@ @Override public void build(final CmdBuilder cb) { cb.init(Cmd.INFO + " " + CmdInfo.STORAGE); - if(args[0] != null && toInt(args[0]) == Integer.MIN_VALUE) { + if(!args[0].isEmpty() && toInt(args[0]) == Integer.MIN_VALUE) { cb.xquery(0); } else { cb.arg(0).arg(1); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/List.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/List.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/List.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/List.java 2015-07-14 10:54:40.000000000 +0000 @@ -32,7 +32,7 @@ /** * Default constructor. - * @param name database name + * @param name database name (can be {@code null}) */ public List(final String name) { this(name, null); @@ -40,21 +40,21 @@ /** * Default constructor. - * @param name database name - * @param path database path + * @param name database name (can be {@code null}) + * @param path database path (can be {@code null}) */ public List(final String name, final String path) { - super(Perm.NONE, name, path); + super(Perm.NONE, name == null ? "" : name, path == null ? "" : path); } @Override protected boolean run() throws IOException { - return args[0] == null || args[0].isEmpty() ? list() : listDB(); + return args[0].isEmpty() ? list() : listDB(); } @Override public void databases(final LockResult lr) { - if(args[0] == null || args[0].isEmpty()) lr.readAll = true; + if(args[0].isEmpty()) lr.readAll = true; else lr.read.add(args[0]); } @@ -75,26 +75,25 @@ for(final String name : context.filter(Perm.READ, context.databases.listDBs())) { String file = null; - long size = 0; - int docs = 0; + long dbsize = 0; + int count = 0; - final MetaData meta = new MetaData(name, options, soptions); try { + final MetaData meta = new MetaData(name, options, soptions); meta.read(); - size = meta.dbsize(); - docs = meta.ndocs; + dbsize = meta.dbsize(); file = meta.original; + // add number of raw files + count = meta.ndocs + new IOFile(soptions.dbpath(name), IO.RAW).descendants().size(); } catch(final IOException ex) { file = ERROR; } - // count number of raw files - final int bin = new IOFile(soptions.dbpath(name), IO.RAW).descendants().size(); // create entry final TokenList tl = new TokenList(create ? 4 : 3); tl.add(name); - tl.add(docs + bin); - tl.add(size); + tl.add(count); + tl.add(dbsize); if(create) tl.add(file); table.contents.add(tl); } @@ -108,8 +107,7 @@ * @throws IOException I/O exception */ private boolean listDB() throws IOException { - final String db = args[0]; - final String path = args[1] != null ? args[1] : ""; + final String db = args[0], path = args[1]; if(!Databases.validName(db)) return error(NAME_INVALID_X, db); final Table table = new Table(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Open.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Open.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Open.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Open.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,6 +8,7 @@ import org.basex.core.locks.*; import org.basex.core.users.*; import org.basex.data.*; +import org.basex.query.value.seq.*; import org.basex.util.*; /** @@ -28,10 +29,10 @@ /** * Default constructor. * @param name name of database - * @param path database path + * @param path database path (can be {@code null}) */ public Open(final String name, final String path) { - super(Perm.NONE, name, path); + super(Perm.NONE, name, path == null ? "" : path); } @Override @@ -48,7 +49,7 @@ context.openDB(data); final String path = args[1]; - if(path != null && !path.isEmpty()) { + if(!path.isEmpty()) { context.current(new DBNodes(data, data.resources.docs(path).toArray())); } if(data.meta.oldindex()) info(H_INDEX_FORMAT); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/OptimizeAll.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/OptimizeAll.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/OptimizeAll.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/OptimizeAll.java 2015-07-14 10:54:40.000000000 +0000 @@ -14,6 +14,7 @@ import org.basex.index.*; import org.basex.io.*; import org.basex.io.serial.*; +import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.util.*; import org.basex.util.list.*; @@ -184,7 +185,7 @@ public void parse(final Builder build) throws IOException { final Serializer ser = new BuilderSerializer(build) { @Override - protected void startOpen(final byte[] name) throws IOException { + protected void startOpen(final QNm name) throws IOException { super.startOpen(name); if(cmd != null) cmd.pre++; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Optimize.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Optimize.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Optimize.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Optimize.java 2015-07-14 10:54:40.000000000 +0000 @@ -116,13 +116,13 @@ elms.push(0); ++n; } else if(kind == Data.ELEM) { - final int id = data.name(pre); + final int id = data.nameId(pre); data.elemNames.index(data.elemNames.key(id), null, true); data.paths.put(id, Data.ELEM, level); pars.push(pre); elms.push(id); } else if(kind == Data.ATTR) { - final int id = data.name(pre); + final int id = data.nameId(pre); final byte[] val = data.text(pre, false); data.attrNames.index(data.attrNames.key(id), val, true); data.paths.put(id, Data.ATTR, level, val, md); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Rename.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Rename.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Rename.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Rename.java 2015-07-14 10:54:40.000000000 +0000 @@ -80,10 +80,10 @@ final String path = string(data.text(pre, true)); if(Prop.CASE ? path.equals(src) : path.equalsIgnoreCase(src)) return trg; - // source references a directory: merge target path and file name - final String name = src.isEmpty() ? path : path.substring(src.length() + 1); - if(trg.isEmpty()) return name; - if(trg.endsWith("/")) return trg + name; - return trg + '/' + name; + // directory references: add trailing slashes to non-empty paths + final String source = src.isEmpty() || src.endsWith("/") ? src : src + '/'; + final String target = trg.isEmpty() || trg.endsWith("/") ? trg : trg + '/'; + // merge target with old path + return target + path.substring(source.length()); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Replace.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Replace.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Replace.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Replace.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,8 +6,8 @@ import org.basex.core.users.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.io.*; +import org.basex.query.up.atomic.*; import org.basex.util.list.*; /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/ShowEvents.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/ShowEvents.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/ShowEvents.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/ShowEvents.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,33 +0,0 @@ -package org.basex.core.cmd; - -import java.io.*; - -import org.basex.core.locks.*; -import org.basex.core.parse.*; -import org.basex.core.parse.Commands.Cmd; -import org.basex.core.parse.Commands.CmdShow; - -/** - * Evaluates the 'show events' command and lists all existing events. - * - * @author BaseX Team 2005-15, BSD License - * @author Roman Raedle - * @author Andreas Weiler - */ -public final class ShowEvents extends AEvent { - @Override - protected boolean run() throws IOException { - out.println(context.events.info()); - return true; - } - - @Override - public void build(final CmdBuilder cb) { - cb.init(Cmd.SHOW + " " + CmdShow.EVENTS).args(); - } - - @Override - public void databases(final LockResult lr) { - lr.read.add(DBLocking.EVENT); - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/ShowUsers.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/ShowUsers.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/ShowUsers.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/ShowUsers.java 2015-07-14 10:54:40.000000000 +0000 @@ -27,17 +27,17 @@ /** * Constructor, specifying a database. - * @param db database (for showing users) + * @param db database (can be {@code null}) */ public ShowUsers(final String db) { - super(Perm.ADMIN, db); + super(Perm.ADMIN, db == null ? "" : db); } @Override protected boolean run() throws IOException { - final String name = args[0] == null || args[0].isEmpty() ? null : args[0]; - if(name != null && !Databases.validName(name)) return error(NAME_INVALID_X, name); - out.println(context.users.info(name).finish()); + final String name = args[0]; + if(!name.isEmpty() && !Databases.validName(name)) return error(NAME_INVALID_X, name); + out.println(context.users.info(name.isEmpty() ? null : name).finish()); return true; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Test.java basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Test.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/cmd/Test.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/cmd/Test.java 2015-07-14 10:54:40.000000000 +0000 @@ -47,7 +47,7 @@ sb.append(suite.skipped).append(' ').append("skipped."); return suite.errors + suite.failures == 0 ? info(sb.toString()) : error(sb.toString()); } catch(final IOException ex) { - cause = ex; + exception = ex; return error(Util.message(ex)); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/Command.java basex-8.2.3/basex-core/src/main/java/org/basex/core/Command.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/Command.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/Command.java 2015-07-14 10:54:40.000000000 +0000 @@ -10,6 +10,7 @@ import org.basex.core.users.*; import org.basex.data.*; import org.basex.io.out.*; +import org.basex.query.value.*; import org.basex.util.*; import org.basex.util.list.*; import org.xml.sax.*; @@ -29,21 +30,25 @@ public final Perm perm; /** Indicates if the command requires an opened database. */ public final boolean openDB; - /** Exception, resulting from command execution. */ - public Exception cause; - /** Performance measurements. */ - protected Performance perf; /** Database context. */ protected Context context; + /** Convenience access to database options. */ + protected MainOptions options; + /** Convenience access to static options. */ + protected StaticOptions soptions; + /** Output stream. */ protected PrintOutput out; /** Optional input source. */ protected InputSource in; - /** Database options. */ - protected MainOptions options; - /** Static options. */ - protected StaticOptions soptions; + + /** Exception, resulting from command execution. */ + protected Exception exception; + /** Performance measurements. */ + protected Performance perf; + /** Maximum number of results (ignored if negative). */ + protected int maxResults = -1; /** Container for query information. */ private final TokenBuilder info = new TokenBuilder(); @@ -94,7 +99,7 @@ // run command and return success flag if(!run(ctx, os)) { final BaseXException ex = new BaseXException(info()); - ex.initCause(cause); + ex.initCause(exception); throw ex; } } catch(final RuntimeException th) { @@ -154,11 +159,18 @@ } /** - * Returns a cached result set generated by {@link AQuery#query}. Will only yield results if - * {@link MainOptions#CACHEQUERY} is set, and can only be called once. + * Returns the cause of an error. + * @return error + */ + public final Exception exception() { + return exception; + } + + /** + * Returns a cached result set generated by {@link AQuery#query}. Can only be called once. * @return result set */ - public Result finish() { + public Value finish() { return null; } @@ -183,8 +195,7 @@ /** * Closes an open data reference and returns {@code true} if this command will change the - * {@link Context#data()} reference. This method is required by the progress dialog in the - * frontend. + * {@link Context#data()} reference. This method is required by the GUI progress dialog. * @param ctx database context * @return result of check */ @@ -194,8 +205,16 @@ } /** - * Returns true if this command returns a progress value. This method is required by the progress - * dialog in the frontend. + * Enforces a maximum number of query results. This method is required by the GUI. + * @param max maximum number of results (ignored if negative) + */ + public final void maxResults(final int max) { + maxResults = max; + } + + /** + * Returns true if this command returns a progress value. + * This method is required by the GUI progress dialog. * @return result of check */ public boolean supportsProg() { @@ -203,8 +222,8 @@ } /** - * Returns true if this command can be stopped. This method is required by the progress dialog in - * the frontend. + * Returns true if this command can be stopped. + * This method is required by the GUI progress dialog. * @return result of check */ public boolean stoppable() { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/Context.java basex-8.2.3/basex-core/src/main/java/org/basex/core/Context.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/Context.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/Context.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,6 +5,7 @@ import org.basex.data.*; import org.basex.io.random.*; import org.basex.query.util.pkg.*; +import org.basex.query.value.seq.*; import org.basex.server.*; import org.basex.util.*; import org.basex.util.list.*; @@ -28,8 +29,6 @@ public final StaticOptions soptions; /** Client sessions. */ public final Sessions sessions; - /** Event pool. */ - public final Events events; /** Opened databases. */ public final Datas datas; /** Users. */ @@ -85,7 +84,6 @@ public Context(final Context ctx) { soptions = ctx.soptions; datas = ctx.datas; - events = ctx.events; sessions = ctx.sessions; databases = ctx.databases; blocker = ctx.blocker; @@ -113,7 +111,6 @@ private Context(final StaticOptions soptions) { this.soptions = soptions; datas = new Datas(); - events = new Events(); sessions = new Sessions(); blocker = new ClientBlocker(); databases = new Databases(soptions); @@ -176,9 +173,7 @@ public DBNodes current() { if(data == null) return null; if(current != null) return current; - final DBNodes nodes = new DBNodes(data, data.resources.docs().toArray()); - nodes.all = true; - return nodes; + return new DBNodes(data, true, data.resources.docs().toArray()); } /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/Databases.java basex-8.2.3/basex-core/src/main/java/org/basex/core/Databases.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/Databases.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/Databases.java 2015-07-14 10:54:40.000000000 +0000 @@ -14,6 +14,9 @@ * @author Jens Erat */ public final class Databases { + /** Date pattern. */ + public static final String DATE = "\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}-\\d{2}"; + /** Allowed characters for database names (additional to letters and digits). * The following characters are invalid: *
    @@ -27,8 +30,7 @@ /** Regex representation of allowed database characters. */ private 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 + '$'); + private static final Pattern ZIPPATTERN = Pattern.compile('-' + DATE + '\\' + IO.ZIPSUFFIX + '$'); /** Regex indicator. */ private static final Pattern REGEX = Pattern.compile(".*[*?,].*"); @@ -145,7 +147,7 @@ if(file.exists()) { backups.add(db); } else { - final Pattern regex = regex(db, DateTime.PATTERN + '\\' + IO.ZIPSUFFIX); + final Pattern regex = regex(db, '-' + DATE + '\\' + IO.ZIPSUFFIX); for(final IOFile f : soptions.dbpath().children()) { final String n = f.name(); if(regex.matcher(n).matches()) backups.add(n.substring(0, n.lastIndexOf('.'))); @@ -162,7 +164,16 @@ * @return name of the database ({@code [dbname]}) */ public static String name(final String backup) { - return Pattern.compile(DateTime.PATTERN + '$').split(backup)[0]; + return Pattern.compile('-' + DATE + '$').split(backup)[0]; + } + + /** + * Extracts the date of a database from the name of a backup. + * @param backup name of the backup file, including the date + * @return date string + */ + public static Date date(final String backup) { + return DateTime.parse(backup.replaceAll("^.+-(" + DATE + ")$", "$1")); } /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/Events.java basex-8.2.3/basex-core/src/main/java/org/basex/core/Events.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/Events.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/Events.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,114 +0,0 @@ -package org.basex.core; - -import static org.basex.core.Text.*; -import static org.basex.util.Token.*; - -import java.io.*; -import java.util.*; - -import org.basex.io.*; -import org.basex.io.in.DataInput; -import org.basex.io.out.DataOutput; -import org.basex.server.*; -import org.basex.util.*; - -/** - * This class organizes all known events. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - * @author Roman Raedle - * @author Andreas Weiler - */ -public final class Events extends HashMap { - /** Event file. */ - private final IOFile file = new IOFile(Prop.HOME, IO.BASEXSUFFIX + "events"); - - /** - * Constructor. - */ - public Events() { - if(!file.exists()) return; - - try(final DataInput in = new DataInput(file)) { - final int s = in.readNum(); - for(int u = 0; u < s; ++u) put(string(in.readToken()), new Sessions()); - } catch(final IOException ex) { - Util.errln(ex); - } - } - - /** - * Creates an event. - * @param name event name - * @return success flag - */ - public synchronized boolean create(final String name) { - final boolean b = put(name, new Sessions()) == null; - if(b) write(); - return b; - } - - /** - * Drops an event. - * @param name event name - * @return success flag - */ - public synchronized boolean drop(final String name) { - final boolean b = remove(name) != null; - if(b) write(); - return b; - } - - /** - * Writes global permissions to disk. - */ - private void write() { - try(final DataOutput out = new DataOutput(file)) { - out.writeNum(size()); - for(final String name : keySet()) out.writeToken(token(name)); - } catch(final IOException ex) { - Util.debug(ex); - } - } - - /** - * Returns information on all events. - * @return information on all events. - */ - public synchronized String info() { - final TokenBuilder tb = new TokenBuilder(); - tb.addExt(EVENTS_X, size()).add(size() == 0 ? DOT : COL); - - final String[] names = keySet().toArray(new String[size()]); - Arrays.sort(names); - for(final String n : names) tb.add(NL).add(LI).add(n); - return tb.toString(); - } - - /** - * Notifies the watching sessions about an event. - * @param ctx database context - * @param name name - * @param msg message - * @return success flag - */ - public synchronized boolean notify(final Context ctx, final byte[] name, final byte[] msg) { - final Sessions sess = get(string(name)); - // event was not found - if(sess == null) return false; - - // refresh timestamp for last interaction - for(final ClientListener srv : sess) { - // ignore active client - if(srv == ctx.listener) continue; - try { - srv.notify(name, msg); - } catch(final IOException ex) { - // remove client if event could not be delivered - sess.remove(srv); - } - } - return true; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/locks/DBLocking.java basex-8.2.3/basex-core/src/main/java/org/basex/core/locks/DBLocking.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/locks/DBLocking.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/locks/DBLocking.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,6 +5,7 @@ import java.util.*; import java.util.Map.Entry; import java.util.concurrent.*; +import java.util.concurrent.atomic.*; import java.util.concurrent.locks.*; import org.basex.core.*; @@ -41,8 +42,6 @@ public static final String ADMIN = PREFIX + "ADMIN"; /** Special lock identifier for backup commands. */ public static final String BACKUP = PREFIX + "BACKUP"; - /** Special lock identifier for event commands. */ - public static final String EVENT = PREFIX + "EVENT"; /** Special lock identifier for repository commands. */ public static final String REPO = PREFIX + "REPO"; @@ -67,7 +66,7 @@ /** Stores one lock for each object used for locking. */ private final Map locks = new HashMap<>(); /** Stores lock usage counters for each object used for locking. */ - private final Map lockUsage = new HashMap<>(); + private final Map lockUsage = new HashMap<>(); /** * Currently running transactions. * Used as monitor for atomizing access to {@link #queue}. @@ -139,7 +138,9 @@ } // global read locking if(read == null) { - while(localWriters > 0) { + while(localWriters > 0 && + // We're the only writer, allow global read lock anyway + !(1 == localWriters && !(null == write || write.isEmpty()))) { try { globalLock.wait(); } catch(final InterruptedException ex) { @@ -247,10 +248,13 @@ * @param lock Lock to set used */ private void setLockUsed(final String lock) { - synchronized(lockUsage) { - Integer usage = lockUsage.get(lock); - if(usage == null) usage = 0; - lockUsage.put(lock, ++usage); + synchronized(locks) { + final AtomicInteger usage = lockUsage.get(lock); + if(usage == null) { + lockUsage.put(lock, new AtomicInteger(1)); + } else { + usage.incrementAndGet(); + } } } @@ -259,14 +263,11 @@ * @param object Object to test */ private void unsetLockIfUnused(final String object) { - synchronized(lockUsage) { - Integer usage = lockUsage.get(object); - assert usage != null; - if(--usage == 0) { + synchronized(locks) { + final AtomicInteger usage = lockUsage.get(object); + if(usage.decrementAndGet() == 0) { locks.remove(object); lockUsage.remove(object); - } else { - lockUsage.put(object, usage); } } } @@ -283,8 +284,10 @@ sb.append(ind + "Transactions running: " + transactions + NL); sb.append(ind + "Transaction queue: " + queue + NL); sb.append(ind + "Held locks by object:" + NL); - for(final Entry e : locks.entrySet()) - sb.append(ind + ind + e.getKey() + " -> " + e.getValue() + NL); + synchronized(locks) { + for(final Entry e : locks.entrySet()) + sb.append(ind + ind + e.getKey() + " -> " + e.getValue() + NL); + } sb.append(ind + "Held write locks by transaction:" + NL); for(final Long thread : writeLocked.keySet()) sb.append(ind + ind + thread + " -> " + writeLocked.get(thread) + NL); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/MainOptions.java basex-8.2.3/basex-core/src/main/java/org/basex/core/MainOptions.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/MainOptions.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/MainOptions.java 2015-07-14 10:54:40.000000000 +0000 @@ -121,8 +121,6 @@ public static final NumberOption TAILCALLS = new NumberOption("TAILCALLS", 256); /** Favor global database when opening resources. */ public static final BooleanOption DEFAULTDB = new BooleanOption("DEFAULTDB", false); - /** Caches the query results. */ - public static final BooleanOption CACHEQUERY = new BooleanOption("CACHEQUERY", false); /** Forces database creation for unknown documents. */ public static final BooleanOption FORCECREATE = new BooleanOption("FORCECREATE", false); /** Validate string inputs. */ @@ -163,9 +161,6 @@ // Other - /** Hidden: maximum number of hits to be displayed in the GUI (will be overwritten). */ - public static final NumberOption MAXHITS = new NumberOption("MAXHITS", -1); - /** Options that are adopted from parent options. */ private static final Option[] PARENT = { CHOP, INTPARSE, STRIPNS, DTD, XINCLUDE, CATFILE }; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/parse/Commands.java basex-8.2.3/basex-core/src/main/java/org/basex/core/parse/Commands.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/parse/Commands.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/parse/Commands.java 2015-07-14 10:54:40.000000000 +0000 @@ -31,8 +31,6 @@ String CREATE_BACKUP = "create-backup"; /** Command string: "create-db". */ String CREATE_DB = "create-db"; - /** Command string: "create-event". */ - String CREATE_EVENT = "create-event"; /** Command string: "create-index". */ String CREATE_INDEX = "create-index"; /** Command string: "create-user". */ @@ -43,8 +41,6 @@ String DROP_BACKUP = "drop-backup"; /** Command string: "drop-db". */ String DROP_DB = "drop-db"; - /** Command string: "drop-event". */ - String DROP_EVENT = "drop-event"; /** Command string: "drop-index". */ String DROP_INDEX = "drop-index"; /** Command string: "drop-user". */ @@ -109,8 +105,6 @@ String SET = "set"; /** Command string: "show-backups". */ String SHOW_BACKUPS = "show-backups"; - /** Command string: "show-events". */ - String SHOW_EVENTS = "show-events"; /** Command string: "show-sessions". */ String SHOW_SESSIONS = "show-sessions"; /** Command string: "show-users". */ @@ -154,15 +148,15 @@ String COMMAND = "command"; /** Create commands. */ - enum CmdCreate { DATABASE, DB, INDEX, USER, BACKUP, EVENT } + enum CmdCreate { DATABASE, DB, INDEX, USER, BACKUP } /** Info commands. */ enum CmdInfo { NULL, DATABASE, DB, INDEX, STORAGE } /** Drop commands. */ - enum CmdDrop { DATABASE, DB, INDEX, USER, BACKUP, EVENT } + enum CmdDrop { DATABASE, DB, INDEX, USER, BACKUP } /** Optimize commands. */ enum CmdOptimize { NULL, ALL } /** Show commands. */ - enum CmdShow { SESSIONS, USERS, BACKUPS, EVENTS } + enum CmdShow { SESSIONS, USERS, BACKUPS } /** Permission commands. */ enum CmdPerm { NONE, READ, WRITE, CREATE, ADMIN } /** Index types. */ diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/parse/StringParser.java basex-8.2.3/basex-core/src/main/java/org/basex/core/parse/StringParser.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/parse/StringParser.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/parse/StringParser.java 2015-07-14 10:54:40.000000000 +0000 @@ -79,8 +79,6 @@ return new CreateIndex(consume(CmdIndex.class, cmd)); case USER: return new CreateUser(name(cmd), password()); - case EVENT: - return new CreateEvent(name(cmd)); } break; case COPY: @@ -144,8 +142,6 @@ return new DropUser(glob(cmd), key(ON, null) ? glob(cmd) : null); case BACKUP: return new DropBackup(glob(cmd)); - case EVENT: - return new DropEvent(name(cmd)); } break; case OPTIMIZE: @@ -193,8 +189,6 @@ return new ShowUsers(key(ON, null) ? name(cmd) : null); case BACKUPS: return new ShowBackups(); - case EVENTS: - return new ShowEvents(); } break; case GRANT: diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/parse/XMLParser.java basex-8.2.3/basex-core/src/main/java/org/basex/core/parse/XMLParser.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/parse/XMLParser.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/parse/XMLParser.java 2015-07-14 10:54:40.000000000 +0000 @@ -12,6 +12,7 @@ import org.basex.io.*; import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.util.*; @@ -55,9 +56,10 @@ * Returns a command. * @param root command node * @return command + * @throws IOException I/O exception * @throws QueryException query exception */ - private Command command(final Item root) throws QueryException { + private Command command(final Item root) throws IOException, QueryException { final String e = ((ANode) root).qname().toJava().toString(); if(e.equals(ADD) && check(root, PATH + '?', '<' + INPUT)) return new Add(value(root, PATH), xml(root)); @@ -77,8 +79,6 @@ return new CreateBackup(value(root, NAME)); if(e.equals(CREATE_DB) && check(root, NAME, '<' + INPUT + '?')) return new CreateDB(value(root, NAME), xml(root)); - if(e.equals(CREATE_EVENT) && check(root, NAME + '?')) - return new CreateEvent(value(root, NAME)); if(e.equals(CREATE_INDEX) && check(root, TYPE)) return new CreateIndex(value(root, TYPE)); if(e.equals(CREATE_USER) && check(root, NAME, '#' + PASSWORD + '?')) @@ -89,8 +89,6 @@ return new DropBackup(value(root, NAME)); if(e.equals(DROP_DB) && check(root, NAME)) return new DropDB(value(root, NAME)); - if(e.equals(DROP_EVENT) && check(root, NAME)) - return new DropEvent(value(root, NAME)); if(e.equals(DROP_INDEX) && check(root, TYPE)) return new DropIndex(value(root, TYPE)); if(e.equals(DROP_USER) && check(root, NAME, PATTERN + '?')) @@ -117,7 +115,7 @@ return new InfoIndex(value(root, TYPE)); if(e.equals(INFO_STORAGE) && check(root, '#' + QUERY + '?')) return new InfoStorage(value(root)); - if(e.equals(KILL) && check(root, TARGET + '?')) + if(e.equals(KILL) && check(root, TARGET)) return new Kill(value(root, TARGET)); if(e.equals(LIST) && check(root, NAME + '?', PATH + '?')) return new List(value(root, NAME), value(root, PATH)); @@ -155,8 +153,6 @@ return new Set(value(root, OPTION), value(root)); if(e.equals(SHOW_BACKUPS) && check(root)) return new ShowBackups(); - if(e.equals(SHOW_EVENTS) && check(root)) - return new ShowEvents(); if(e.equals(SHOW_SESSIONS) && check(root)) return new ShowSessions(); if(e.equals(SHOW_USERS) && check(root, DATABASE + '?')) @@ -174,7 +170,7 @@ * Returns the value of the specified attribute. * @param root root node * @param att name of attribute - * @return query exception + * @return value * @throws QueryException query exception */ private String value(final Item root, final String att) throws QueryException { @@ -184,7 +180,7 @@ /** * Returns a string value (text node). * @param root root node - * @return query exception + * @return string value * @throws QueryException query exception */ private String value(final Item root) throws QueryException { @@ -194,23 +190,24 @@ /** * Returns a password (text node). * @param root root node - * @return query exception + * @return password string * @throws QueryException query exception */ private String password(final Item root) throws QueryException { final String pw = execute("string(.)", root); - return pw.isEmpty() && pwReader != null ? pwReader.password() : ""; + return pw.isEmpty() && pwReader != null ? pwReader.password() : pw; } /** * Returns an xml value (text node). * @param root root node - * @return query exception + * @return xml value + * @throws IOException I/O exception * @throws QueryException query exception */ - private String xml(final Item root) throws QueryException { + private String xml(final Item root) throws IOException, QueryException { try(final QueryProcessor qp = new QueryProcessor("node()", ctx)) { - return qp.context(root).execute().toString().trim(); + return qp.context(root).value().serialize().toString().trim(); } } @@ -218,7 +215,7 @@ * Executes the specified query and returns a string representation. * @param query query * @param context context node - * @return query exception + * @return string representation * @throws QueryException query exception */ private String execute(final String query, final Item context) throws QueryException { @@ -289,19 +286,20 @@ } // run query + final Value mv = ma.value(), ov = oa.value(); try(final QueryProcessor qp = new QueryProcessor(tb.toString(), ctx).context(root)) { - qp.bind("A", ma.value()).bind("O", oa.value()); - if(!qp.execute().toString().isEmpty()) return true; + qp.bind("A", mv).bind("O", ov); + if(qp.value().size() != 0) return true; } // build error string final TokenBuilder syntax = new TokenBuilder(); final byte[] nm = ((ANode) root).qname().string(); syntax.reset().add('<').add(nm); - for(final Item i : ma) { + for(final Item i : mv) { final byte[] a = i.string(null); syntax.add(' ').add(a).add("=\"...\""); } - for(final Item i : oa) { + for(final Item i : ov) { final byte[] a = i.string(null); syntax.add(" (").add(a).add("=\"...\")"); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/Sandbox.java basex-8.2.3/basex-core/src/main/java/org/basex/core/Sandbox.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/Sandbox.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/Sandbox.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,172 @@ +package org.basex.core; + +import static org.basex.core.Text.*; + +import java.io.*; +import java.util.concurrent.*; + +import org.basex.*; +import org.basex.api.client.*; +import org.basex.core.users.*; +import org.basex.io.*; +import org.basex.io.out.*; +import org.basex.util.*; +import org.basex.util.list.*; + +/** + * If this class is extended, tests will be run in a sandbox. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public abstract class Sandbox { + /** Database port. */ + protected static final int DB_PORT = 9996; + + /** Default output stream. */ + public static final PrintStream OUT = System.out; + /** Default error stream. */ + public static final PrintStream ERR = System.err; + /** Null output stream. */ + public static final PrintStream NULL = new PrintStream(new NullOutput()); + /** Test name. */ + protected static final String NAME = Util.className(Sandbox.class); + /** Database context. */ + protected static Context context; + + /** + * Creates the sandbox. + */ + public static void initSandbox() { + final IOFile sb = sandbox(); + sb.delete(); + if(!sb.md()) throw Util.notExpected("Sandbox could not be created."); + + final String path = sb.path(); + Prop.put(StaticOptions.DBPATH, path + "/data"); + Prop.put(StaticOptions.WEBPATH, path + "/webapp"); + Prop.put(StaticOptions.RESTXQPATH, path + "/webapp"); + Prop.put(StaticOptions.REPOPATH, path + "/repo"); + Prop.put(StaticOptions.SERVERPORT, DB_PORT); + context = new Context(); + } + + /** + * Removes test databases and closes the database context. + */ + public static void finishSandbox() { + context.close(); + Prop.remove(StaticOptions.DBPATH); + Prop.remove(StaticOptions.WEBPATH); + Prop.remove(StaticOptions.RESTXQPATH); + Prop.remove(StaticOptions.REPOPATH); + Prop.remove(StaticOptions.SERVERPORT); + if(!sandbox().delete()) throw Util.notExpected("Sandbox could not be created."); + } + + /** + * Creates a new, sandboxed server instance. + * @param args additional arguments + * @return server instance + * @throws IOException I/O exception + */ + public static BaseXServer createServer(final String... args) throws IOException { + try { + System.setOut(NULL); + final StringList sl = new StringList("-z", "-p" + DB_PORT, "-q"); + for(final String arg : args) sl.add(arg); + final BaseXServer server = new BaseXServer(sl.finish()); + server.context.soptions.set(StaticOptions.DBPATH, sandbox().path()); + return server; + } finally { + System.setOut(OUT); + } + } + + /** + * Stops a server instance. + * @param server server + * @throws IOException I/O exception + */ + public static void stopServer(final BaseXServer server) throws IOException { + try { + System.setOut(NULL); + if(server != null) server.stop(); + } finally { + System.setOut(OUT); + } + } + + /** + * Creates a client instance. + * @param login optional login data + * @return client instance + * @throws IOException I/O exception + */ + public static ClientSession createClient(final String... login) throws IOException { + final String user = login.length > 0 ? login[0] : UserText.ADMIN; + final String pass = login.length > 1 ? login[1] : UserText.ADMIN; + return new ClientSession(S_LOCALHOST, DB_PORT, user, pass); + } + + /** + * Returns the sandbox database path. + * @return database path + */ + public static IOFile sandbox() { + return new IOFile(Prop.TMP, NAME); + } + + /** + * Normalizes newlines in a query result. + * @param result input string + * @return normalized string + */ + public static String normNL(final String result) { + return result.replaceAll("(\r?\n|\r) *", "\n"); + } + + /** Client. */ + public static final class Client extends Thread { + /** Start signal. */ + private final CountDownLatch startSignal; + /** Stop signal. */ + private final CountDownLatch stopSignal; + /** Client session. */ + private final ClientSession session; + /** Command string. */ + private final Command cmd; + /** Fail flag. */ + public String error; + + /** + * Client constructor. + * @param c command string to execute + * @param start start signal + * @param stop stop signal + * @throws IOException I/O exception while establishing the session + */ + public Client(final Command c, final CountDownLatch start, final CountDownLatch stop) + throws IOException { + + session = createClient(); + cmd = c; + startSignal = start; + stopSignal = stop; + start(); + } + + @Override + public void run() { + try { + if(startSignal != null) startSignal.await(); + session.execute(cmd); + session.close(); + } catch(final Throwable ex) { + error = "\n" + cmd + '\n' + ex; + } finally { + if(stopSignal != null) stopSignal.countDown(); + } + } + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/StaticOptions.java basex-8.2.3/basex-core/src/main/java/org/basex/core/StaticOptions.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/StaticOptions.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/StaticOptions.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,5 @@ package org.basex.core; -import static org.basex.util.Prop.*; - import java.util.*; import org.basex.io.*; @@ -17,7 +15,7 @@ */ public final class StaticOptions extends Options { /** Indicates if the user's home directory has been chosen as home directory. */ - private static final boolean USERHOME = HOME.equals(Prop.USERHOME); + private static final boolean USERHOME = Prop.HOME.equals(Prop.USERHOME); /** Comment: written to options file. */ public static final Comment C_GENERAL = new Comment("General Options"); @@ -26,12 +24,12 @@ public static final BooleanOption DEBUG = new BooleanOption("DEBUG", false); /** Database path. */ public static final StringOption DBPATH = new StringOption("DBPATH", - HOME + (USERHOME ? NAME + "Data" : "data")); + Prop.HOME + (USERHOME ? Prop.NAME + "Data" : "data")); /** Package repository path. */ public static final StringOption REPOPATH = new StringOption("REPOPATH", - HOME + (USERHOME ? NAME + "Repo" : "repo")); + Prop.HOME + (USERHOME ? Prop.NAME + "Repo" : "repo")); /** Language name. */ - public static final StringOption LANG = new StringOption("LANG", language); + public static final StringOption LANG = new StringOption("LANG", Prop.language); /** Flag to include key names in the language strings. */ public static final BooleanOption LANGKEYS = new BooleanOption("LANGKEYS", false); /** Applied locking algorithm: local (database) vs. global (process) locking. */ @@ -46,8 +44,6 @@ public static final NumberOption PORT = new NumberOption("PORT", 1984); /** Server: port, used for binding the server. */ public static final NumberOption SERVERPORT = new NumberOption("SERVERPORT", 1984); - /** Server: port, used for sending events. */ - public static final NumberOption EVENTPORT = new NumberOption("EVENTPORT", 1985); /** Default user. */ public static final StringOption USER = new StringOption("USER", ""); /** Default password. */ @@ -79,11 +75,13 @@ /** Web path. */ public static final StringOption WEBPATH = new StringOption("WEBPATH", - HOME + (USERHOME ? NAME + "Web" : "webapp")); + Prop.HOME + (USERHOME ? Prop.NAME + "Web" : "webapp")); /** REST path (relative to web path). */ public static final StringOption RESTPATH = new StringOption("RESTPATH", ""); /** RESTXQ path (relative to web path). */ public static final StringOption RESTXQPATH = new StringOption("RESTXQPATH", ""); + /** Cache RESTXQ paths. */ + public static final BooleanOption CACHERESTXQ = new BooleanOption("CACHERESTXQ", false); /** Local (embedded) mode. */ public static final BooleanOption HTTPLOCAL = new BooleanOption("HTTPLOCAL", false); /** Port for stopping the web server. */ @@ -109,26 +107,26 @@ * @param file if {@code true}, options will be read from disk */ StaticOptions(final boolean file) { - super(file ? new IOFile(HOME, IO.BASEXSUFFIX) : null); + super(file ? new IOFile(Prop.HOME, IO.BASEXSUFFIX) : null); setSystem(); // set some static options - language = get(LANG); - langkeys = get(LANGKEYS); - debug = get(DEBUG); + Prop.language = get(LANG); + Prop.langkeys = get(LANGKEYS); + Prop.debug = get(DEBUG); final String ph = get(PROXYHOST); if(!ph.isEmpty()) { - setSystem("http.proxyHost", ph); - setSystem("https.proxyHost", ph); + Prop.setSystem("http.proxyHost", ph); + Prop.setSystem("https.proxyHost", ph); } final String pp = Integer.toString(get(PROXYPORT)); if(!pp.equals("0")) { - setSystem("http.proxyPort", pp); - setSystem("https.proxyPort", pp); + Prop.setSystem("http.proxyPort", pp); + Prop.setSystem("https.proxyPort", pp); } final String nph = get(NONPROXYHOSTS); if(!nph.isEmpty()) { - setSystem("http.nonProxyHosts", nph); + Prop.setSystem("http.nonProxyHosts", nph); } if(get(IGNORECERT)) IOUrl.ignoreCert(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/Text.java basex-8.2.3/basex-core/src/main/java/org/basex/core/Text.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/Text.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/Text.java 2015-07-14 10:54:40.000000000 +0000 @@ -55,7 +55,7 @@ String S_STANDALONE = "Standalone"; /** Start information. */ String S_LOCALINFO = - " [-bcdioqrRsuvVwxXz] [input]" + NL + + " [-bcdiIoqrRstuvVwxXz] [input]" + NL + " [input] XQuery or command file, or query string" + NL + " -b Bind external query variables" + NL + " -c Execute commands from file or string" + NL + @@ -79,7 +79,7 @@ String S_CLIENT = "Client"; /** Client start information. */ String S_CLIENTINFO = - " [-bcdinopPqrRsUvVwxz] [input]" + NL + + " [-bcdiInopPqrRsUvVwxXz] [input]" + NL + " [input] XQuery or command file, or query string" + NL + " -b Bind external query variables" + NL + " -c Execute commands from file or string" + NL + @@ -105,11 +105,10 @@ String S_SERVER = "Server"; /** Server start information. */ String S_SERVERINFO = - " [-cdeinpSz] [stop]" + NL + + " [-cdinpSz] [stop]" + NL + " stop Stop running server" + NL + " -c Execute initial database commands" + NL + " -d Activate debugging mode" + NL + - " -e Set event port" + NL + " -n Set host the server is bound to" + NL + " -p Set server port" + NL + " -S Start as service" + NL + @@ -124,10 +123,9 @@ /** HTTP information. */ String S_HTTPINFO = - " [-dehlnpPsSUz] [stop]" + NL + + " [-dhlnpPsSUz] [stop]" + NL + " stop Stop running server" + NL + " -d Activate debugging mode" + NL + - " -e Set event port" + NL + " -h Set port of HTTP server" + NL + " -l Start in local mode" + NL + " -n Set host name of database server" + NL + @@ -241,19 +239,23 @@ // SERVER =================================================================== /** Server was started. */ - String SRV_STARTED_PORT_X = lang("srv_started_port_%"); + String PORT_X = " (" + lang("port") + ": %)."; + /** Server was started. */ + String SRV_STARTED_PORT_X = lang("srv_started") + PORT_X; /** Server was stopped. */ - String SRV_STOPPED_PORT_X = lang("srv_stopped_port_%"); + String SRV_STOPPED_PORT_X = lang("srv_stopped") + PORT_X; + /** Server is running or permission was denied. */ + String SRV_RUNNING = lang("srv_running") + '.'; /** Server is running or permission was denied. */ - String SRV_RUNNING = lang("srv_running"); - /** Ports was specified twice. */ - String PORT_TWICE_X = lang("port_twice_%"); + String SRV_RUNNING_X = lang("srv_running") + PORT_X; + /** Connection error. */ + String CONNECTION_ERROR = lang("connection_error") + '.'; + /** Connection error. */ + String CONNECTION_ERROR_X = lang("connection_error") + PORT_X; /** Unknown host. */ String UNKNOWN_HOST_X = lang("unknown_host_x"); /** Timeout exceeded. */ String TIMEOUT_EXCEEDED = lang("timeout_exceeded"); - /** Connection error. */ - String CONNECTION_ERROR = lang("connection_error"); /** Access denied. */ String ACCESS_DENIED = lang("access_denied"); /** User name. */ @@ -277,15 +279,13 @@ /** Command help. */ String[] HELPCREATE = { "[" + CmdCreate.BACKUP + '|' + CmdCreate.DATABASE + '|' + - CmdCreate.EVENT + '|' + CmdCreate.INDEX + '|' + CmdCreate.USER + "] [...]", + CmdCreate.INDEX + '|' + CmdCreate.USER + "] [...]", lang("c_create1"), lang("c_create2") + NL + LI + CmdDrop.BACKUP + " [" + S_NAME + "]:" + NL + " " + lang("c_create22", S_NAME) + NL + LI + CmdCreate.DATABASE + " [" + S_NAME + "] ([" + S_INPUT + "]):" + NL + " " + lang("c_create21", S_NAME, S_INPUT) + NL + - LI + CmdCreate.EVENT + " [" + S_NAME + "]: " + NL + - " " + lang("c_create25") + NL + LI + CmdCreate.INDEX + " [" + CmdIndex.TEXT + '|' + CmdIndex.ATTRIBUTE + '|' + CmdIndex.FULLTEXT + "]:" + NL + " " + lang("c_create23") + NL + @@ -345,7 +345,7 @@ }; /** Command help. */ String[] HELPDROP = { - "[" + CmdDrop.BACKUP + '|' + CmdDrop.DATABASE + '|' + CmdDrop.EVENT + '|' + + "[" + CmdDrop.BACKUP + '|' + CmdDrop.DATABASE + '|' + CmdDrop.INDEX + '|' + CmdDrop.USER + "] [...]", lang("c_drop1"), lang("c_drop2") + NL + @@ -353,8 +353,6 @@ " " + lang("c_drop24") + NL + LI + CmdDrop.DATABASE + " [" + S_NAME + "]:" + NL + " " + lang("c_drop21") + NL + - LI + CmdDrop.EVENT + " [" + S_NAME + "]:" + NL + - " " + lang("c_drop25") + NL + LI + CmdDrop.INDEX + " [" + CmdIndex.TEXT + '|' + CmdIndex.ATTRIBUTE + '|' + CmdIndex.FULLTEXT + "]:" + NL + " " + lang("c_drop22") + NL + @@ -413,11 +411,9 @@ }; /** Command help. */ String[] HELPSHOW = { - "[" + CmdShow.BACKUPS + '|' + CmdShow.EVENTS + - '|' + CmdShow.SESSIONS + '|' + CmdShow.USERS + ']', + "[" + CmdShow.BACKUPS + '|' + CmdShow.SESSIONS + '|' + CmdShow.USERS + ']', lang("c_show1"), lang("c_show21") + NL + - LI + CmdShow.EVENTS + ": " + lang("c_show26") + NL + LI + CmdShow.SESSIONS + ": " + lang("c_show23") + NL + LI + CmdShow.USERS + " (" + ON + " [database]): " + lang("c_show24") + NL + LI + CmdShow.BACKUPS + ": " + lang("c_show25") @@ -573,7 +569,7 @@ String DB_NOT_FOUND_X = lang("db_not_found_%"); /** Name invalid. */ String NAME_INVALID_X = lang("name_invalid_%"); - /** Paht invalid. */ + /** Path invalid. */ String PATH_INVALID_X = lang("path_invalid_%"); /** Database pinned. */ String DB_PINNED_X = lang("db_pinned_%"); @@ -692,8 +688,6 @@ /** Show sessions. */ String SESSIONS_X = lang("sessions_%"); - /** Show events. */ - String EVENTS_X = lang("events_%"); /** Show packages. */ String PACKAGES_X = lang("packages_%"); /** Permission required. */ @@ -726,22 +720,6 @@ String SESSIONS_KILLED_X = lang("sessions_killed_%"); /** User kills itself. */ String KILL_SELF_X = lang("kill_self_%"); - /** Event dropped. */ - String EVENT_DROPPED_X = lang("event_dropped_%"); - /** Event added. */ - String EVENT_CREATED_X = lang("event_created_%"); - /** Event not found. */ - String EVENT_UNKNOWN_X = lang("event_unknown_%"); - /** Already watching the event. */ - String EVENT_WATCHED_X = lang("event_watched_%"); - /** Nothing to unwatch. */ - String EVENT_NOT_WATCHED_X = lang("event_not_watched_%"); - /** Event already exists. */ - String EVENT_EXISTS_X = lang("event_exists_%"); - /** Watch Event. */ - String WATCHING_EVENT_X = lang("watching_event_%"); - /** Unwatch Event. */ - String UNWATCHING_EVENT_X = lang("unwatching_event_%"); /** Package deleted. */ String PKG_DELETED_X = lang("pkg_deleted_%"); /** Package installed. */ diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/users/User.java basex-8.2.3/basex-core/src/main/java/org/basex/core/users/User.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/users/User.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/users/User.java 2015-07-14 10:54:40.000000000 +0000 @@ -201,10 +201,10 @@ /** * Sets the permission. * @param prm permission - * @param pattern database pattern (can be {@code null}) + * @param pattern database pattern (can be empty) */ public synchronized void perm(final Perm prm, final String pattern) { - if(pattern == null) { + if(pattern.isEmpty()) { perm = prm; } else { locals.put(pattern, prm); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/core/users/Users.java basex-8.2.3/basex-core/src/main/java/org/basex/core/users/Users.java --- basex-8.1.1/basex-core/src/main/java/org/basex/core/users/Users.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/core/users/Users.java 2015-07-14 10:54:40.000000000 +0000 @@ -117,7 +117,7 @@ * Sets the permission of a user. * @param user user * @param prm permission - * @param pattern database pattern (can be {@code null}) + * @param pattern database pattern (can be empty) */ public static void perm(final User user, final Perm prm, final String pattern) { user.perm(prm, pattern); @@ -135,13 +135,13 @@ } /** - * Drops a user from the list. + * Drops a user from the list, or removes the specified pattern if non-empty. * @param user user reference - * @param pattern database pattern (can be {@code null}) + * @param pattern database pattern (can be empty) * @return success flag */ public synchronized boolean drop(final User user, final String pattern) { - if(pattern == null) { + if(pattern.isEmpty()) { if(users.remove(user.name()) == null) return false; } else { user.remove(pattern); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/AtomicUpdateCache.java basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/AtomicUpdateCache.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/AtomicUpdateCache.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/AtomicUpdateCache.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,519 +0,0 @@ -package org.basex.data.atomic; - -import java.util.*; - -import org.basex.data.*; -import org.basex.util.*; -import org.basex.util.hash.*; - -/** - * Implementation of the Atomic Update Cache (AUC). - * - *

    A container/list for atomic updates. Updates must be added from the lowest to - * the highest PRE value (regarding the location of the update). Updates are finally - * applied by this container from the highest to the lowest PRE value (reverse document - * order) to support efficient structural bulk updates etc.

    - * - *

    If a collection of updates is carried out via the AUC there are several - * benefits:

    - * - *
      - *
    1. Efficient distance adjustments after structural changes.
    2. - *
    3. Tree-Aware Updates (TAU): identification of superfluous updates (like updating - * the descendants of a deleted node).
    4. - *
    5. Resolution of text node adjacency.
    6. - *
    7. Merging of atomic updates to reduce number of I/Os.
    8. - *
    - * - *

    To avoid ambiguity it is not allowed to add:

    - *
      - *
    • more than one destructive update like {@link Delete} or {@link Replace} operating - * on the same node.
    • - *
    • more than one {@link Rename} or {@link UpdateValue} operating - * on the same node.
    • - *
    • sequences like [delete X, insert N at X]: This sequence would be carried out back - * to front: first the insert, then the delete. This would lead to the inserted node N - * being deleted by the 'delete X' statement. The correct order for this sequence would - * be [insert N at X, delete X].
    • - *
    • and so forth ... see check() function for details.
    • - *
    - * - *

    Updates are added in a streaming fashion where the most recently added update is - * remembered. This avoids additional traversals of the AUC during consistency checks and - * further optimizations.

    - * - * @author BaseX Team 2005-15, BSD License - * @author Lukas Kircher - */ -public final class AtomicUpdateCache { - /** List of structural updates (nodes are inserted to / deleted from the table. */ - private final List struct = new ArrayList<>(1); - /** Value / non-structural updates like rename. */ - private final List val = new ArrayList<>(1); - /** Most recently added update buffer. Used to merge/discard updates and to detect - * inconsistencies on-the-fly eliminating the need to traverse all updates. */ - private BasicUpdate recent; - /** Most recently added structural atomic update - if there is any. Used to calculate accumulated - * pre value shifts on-the-fly, as {@link BasicUpdate} don't carry this information. */ - private BasicUpdate recentStruct; - /** Target data reference. */ - public final Data data; - - /** - * Constructor. - * @param data target data reference - */ - public AtomicUpdateCache(final Data data) { - this.data = data; - } - - /** - * Adds a delete atomic to the list. - * @param pre PRE value of the target node/update location - */ - public void addDelete(final int pre) { - considerAtomic(Delete.getInstance(data, pre), false); - } - - /** - * Adds an insert atomic to the list. - * @param pre PRE value of the target node/update location - * @param par new parent of the inserted nodes - * @param clip insertion sequence data clip - */ - public void addInsert(final int pre, final int par, final DataClip clip) { - considerAtomic(clip.data.kind(clip.start) == Data.ATTR - ? InsertAttr.getInstance(pre, par, clip) : Insert.getInstance(pre, par, clip), false); - } - - /** - * Adds a replace atomic to the list. - * @param pre PRE value of the target node/update location - * @param clip insertion sequence data clip - */ - public void addReplace(final int pre, final DataClip clip) { - considerAtomic(Replace.getInstance(data, pre, clip), false); - } - - /** - * Adds a rename atomic to the list. - * @param pre PRE value of the target node/update location - * @param name new name for the target node - * @param uri new uri for the target node - */ - public void addRename(final int pre, final byte[] name, final byte[] uri) { - considerAtomic(Rename.getInstance(data, pre, name, uri), false); - } - - /** - * Adds an updateValue atomic to the list. - * @param pre PRE value of the target node/update location - * @param value new value for the target node - */ - public void addUpdateValue(final int pre, final byte[] value) { - considerAtomic(UpdateValue.getInstance(data, pre, value), false); - } - - /** - * Resets the list. - */ - public void clear() { - struct.clear(); - val.clear(); - recent = null; - recentStruct = null; - } - - /** - * Adds an update to the corresponding list. - * @param candidate atomic update - * @param slack skip consistency checks etc. if true (used during text node merging) - */ - private void considerAtomic(final BasicUpdate candidate, final boolean slack) { - // fill the one-atomic-update buffer - if(recent == null) { - recent = candidate; - if(recent instanceof StructuralUpdate) - recentStruct = candidate; - return; - } - - if(candidate instanceof StructuralUpdate && recentStruct != null) { - ((StructuralUpdate) candidate).accumulatedShifts += recentStruct.accumulatedShifts(); - } - - // prepare & optimize incoming update - if(slack) { - add(candidate, false); - } else { - check(recent, candidate); - if(treeAwareUpdates(recent, candidate)) return; - - final BasicUpdate m = recent.merge(data, candidate); - if(m != null) add(m, true); - else add(candidate, false); - - } - } - - /** - * Adds the given update to the updates/buffer depending on the type and whether it's - * been merged or not. - * - * @param bu update - * @param merged if true, the given update has been merged w/ the recent one - */ - private void add(final BasicUpdate bu, final boolean merged) { - if(bu == null) return; - - if(!merged) { - if(recent instanceof StructuralUpdate) - struct.add((StructuralUpdate) recent); - else val.add(recent); - } - recent = bu; - if(bu instanceof StructuralUpdate) - recentStruct = bu; - } - - /** - * Flushes the buffer that contains the most previously added atomic update. - */ - private void flush() { - if(recent != null) { - add(recent, false); - recent = null; - recentStruct = null; - } - } - - /** - * Returns the number of structural updates. - * @return number of structural updates - */ - public int updatesSize() { - flush(); - return struct.size() + val.size(); - } - - /** - * Checks the given sequence of two updates for violations. - * - * Updates must be ordered strictly from the lowest to the highest PRE value. - * Deletes must follow inserts. - * - * A single node must not be affected by more than one {@link Rename}, - * {@link UpdateValue} operation. - * - * A single node must not be affected by more than one destructive operation. These - * operations include {@link Replace}, {@link Delete}. - * - * @param bu1 first update in sequence - * @param bu2 second update in sequence - */ - private static void check(final BasicUpdate bu1, final BasicUpdate bu2) { - // check order of location PRE, must be strictly ordered low-to-high - if(bu2.location < bu1.location) - throw Util.notExpected("Invalid order at location " + bu1.location); - - if(bu2.location == bu1.location) { - // check invalid sequence of {@link Delete}, {@link Insert} - // - the inserted node would directly be deleted without this restriction - if(bu2 instanceof Insert || bu2 instanceof InsertAttr) - if(bu1 instanceof Delete) - throw Util.notExpected("Invalid sequence of delete, insert at location " + bu1.location); - else if(bu1 instanceof Replace) - throw Util.notExpected("Invalid sequence of replace, insert at location " + bu1.location); - - // check multiple {@link Delete}, {@link Replace} - if(bu2.destructive() && bu1.destructive()) - throw Util.notExpected("Multiple deletes/replaces on node " + bu1.location); - - // check multiple {@link Rename} - if(bu2 instanceof Rename && bu1 instanceof Rename) - throw Util.notExpected("Multiple renames on node " + bu1.location); - - // check multiple {@link UpdateValue} - if(bu2 instanceof UpdateValue && bu1 instanceof UpdateValue) - throw Util.notExpected("Multiple updates on node " + bu1.location); - - /* Check invalid order of destructive/non-destructive updates to support TAU - * cases like: : node X would be deleted and then X+1 renamed, - * as this shifts down to X. - */ - if(bu2.destructive() && !(bu1 instanceof StructuralUpdate)) - throw Util.notExpected("Invalid sequence of value update and destructive update at" + - " location " + bu1.location); - } - } - - /** - * Checks if the second update is superfluous. An update is considered to be superfluous - * if it targets a position in the subtree of a to-be-removed node. - * @param bu1 first update in sequence - * @param bu2 second update in sequence - * @return true if second update superfluous - */ - private boolean treeAwareUpdates(final BasicUpdate bu1, final BasicUpdate bu2) { - if(bu1.destructive()) { - // we determine the lowest and highest PRE values of a superfluous update - final int pre = bu1.location; - final int fol = pre + data.size(pre, data.kind(pre)); - /* CASE 1: candidate operates on the subtree of T and appends a node to the end of - * the subtree (target PRE may be equal)... - * CASE 2: operates within subtree of T */ - if(bu2.location <= fol && (bu2 instanceof Insert || bu2 instanceof InsertAttr) && - bu2.parent >= pre && bu2.parent < fol || - bu2.location < fol) { - return true; - } - } - return false; - } - - /** - * Executes the updates. Resolving text node adjacency can be skipped if adjacent text - * nodes are not to be expected. - * @param mergeTexts adjacent text nodes are to be expected and must be merged - */ - public void execute(final boolean mergeTexts) { - data.cache = true; - applyUpdates(); - adjustDistances(); - if(mergeTexts) resolveTextAdjacency(); - data.cache = false; - clear(); - } - - /** - * Carries out structural updates. - */ - public void applyUpdates() { - // check if previous update still in buffer - flush(); - // value updates applied front-to-back, doens't matter as there are no row shifts - for(final BasicUpdate u : val) u.apply(data); - // structural updates are applied back-to-front - for(int i = struct.size() - 1; i >= 0; i--) struct.get(i).apply(data); - } - - /** - * Adjusts distances to restore parent-child relationships that have been invalidated - * by structural updates. - * - * Each structural update (insert/delete) leads to a shift of higher PRE values. This - * invalidates parent-child relationships. Distances are only adjusted after all - * structural updates have been carried out to make sure each node (that has to be - * updated) is only touched once. - */ - private void adjustDistances() { - // check if any distance has changed at all - boolean shifts = false; - for(final StructuralUpdate update : struct) { - if(update.accumulatedShifts != 0) { - shifts = true; - break; - } - } - if(!shifts) return; - - final IntSet updatedNodes = new IntSet(); - for(final StructuralUpdate update : struct) { - /* Update distance for the affected node and all following siblings of nodes - * on the ancestor-or-self axis. */ - int pre = update.preOfAffectedNode + update.accumulatedShifts; - while(pre < data.meta.size && !updatedNodes.contains(pre)) { - final int kind = data.kind(pre); - data.dist(pre, kind, calculateNewDistance(pre, kind)); - updatedNodes.add(pre); - pre += data.size(pre, kind); - } - } - } - - /** - * Calculates the new distance value for the given node after updates have been applied. - * @param pre the new PRE value of the node after structural updates have been applied - * @param kind the KIND value - * @return new distance for the given PRE node - */ - private int calculateNewDistance(final int pre, final int kind) { - int distanceBefore = data.dist(pre, kind); - final int preBefore = calculatePreValue(pre, true); - // document distances are not stored in table but calculated on the fly (always pre+1) - if(kind == Data.DOC) distanceBefore = preBefore + 1; - final int parentBefore = preBefore - distanceBefore; - final int parentAfter = calculatePreValue(parentBefore, false); - return pre - parentAfter; - } - - /** - * Calculates the PRE value of a given node before/after updates. - * - * Finds all updates that affect the given node N. The result is than calculated based - * on N and the accumulated PRE value shifts introduced by these updates. - * - * If a node has been inserted at position X and this method is used to calculate the - * PRE value of X before updates, X is the result. As the node at position X has not - * existed before the insertion, its PRE value is unchanged. If in contrast the PRE - * value is calculated after updates, the result is X+1, as the node with the original - * position X has been shifted by the insertion at position X. - * - * Make sure accumulated shifts have been calculated before calling this method! - * - * @param pre PRE value - * @param beforeUpdates calculate PRE value before shifts/updates have been applied - * @return index of update, or -1 - */ - public int calculatePreValue(final int pre, final boolean beforeUpdates) { - // find update that affects the given PRE value - int i = find(pre, beforeUpdates); - // given PRE not changed by updates - if(i == -1) return pre; - // refine the search to determine accumulated shifts for the given PRE - i = refine(struct, i, beforeUpdates); - final int acm = struct.get(i).accumulatedShifts; - return beforeUpdates ? pre - acm : pre + acm; - } - - /** - * Used to find the update that holds the accumulated shift value that is needed to - * recalculate the given PRE value. In a low-to-high ordered list this is the right-most - * update with a target PRE value smaller or equal the given PRE value, v.v. - * - * Finds the position of the update that affects the given PRE value P. - * If there are multiple updates whose affected PRE value equals P, the search - * has to be further refined as this method returns only the first match. - * @param pre given PRE value - * @param beforeUpdates compare based on PRE values before/after updates - * @return index of update - */ - private int find(final int pre, final boolean beforeUpdates) { - int left = 0; - int right = struct.size() - 1; - - while(left <= right) { - if(left == right) { - if(c(struct, left, beforeUpdates) <= pre) return left; - return -1; - } - if(right - left == 1) { - if(c(struct, right, beforeUpdates) <= pre) return right; - if(c(struct, left, beforeUpdates) <= pre) return left; - return -1; - } - final int middle = left + right >>> 1; - final int value = c(struct, middle, beforeUpdates); - if(value == pre) return middle; - else if(value > pre) right = middle - 1; - else left = middle; - } - - // empty array - return -1; - } - - /** - * Finds the update with the lowest index in the given list that affects the same - * PRE value as the update with the given index. - * @param updates list of updates - * @param index of update - * @param beforeUpdates find update for PRE values before updates have been applied - * @return update with the highest index that invalidates the distance of the given - * node - */ - private static int refine(final List updates, final int index, - final boolean beforeUpdates) { - int u = index; - final int value = c(updates, u++, beforeUpdates); - final int us = updates.size(); - while(u < us && c(updates, u, beforeUpdates) == value) u++; - return u - 1; - } - - /** - * Recalculates the PRE value of the first node whose distance is affected by the - * given update. - * @param updates list of updates - * @param index index of the update - * @param beforeUpdates calculate PRE value before or after updates - * @return PRE value - */ - private static int c(final List updates, final int index, - final boolean beforeUpdates) { - final StructuralUpdate u = updates.get(index); - return u.preOfAffectedNode + (beforeUpdates ? u.accumulatedShifts : 0); - } - - /** - * Resolves unwanted text node adjacency which can result from structural changes in - * the database. Adjacent text nodes are two text nodes A and B, where - * PRE(B)=PRE(A)+1 and PARENT(A)=PARENT(B). - */ - private void resolveTextAdjacency() { - // Text node merges are also gathered on a separate list to leverage optimizations. - final List deletes = new LinkedList<>(); - - // keep track of the visited locations to avoid superfluous checks - int smallestVisited = Integer.MAX_VALUE; - // Text nodes have to be merged from the highest to the lowest pre value - for(int i = struct.size() - 1; i >= 0; i--) { - final StructuralUpdate u = struct.get(i); - final DataClip insseq = u.getInsertionData(); - // calculate the new location of the update, here we have to check for adjacency - final int newLocation = u.location + u.accumulatedShifts - u.shifts; - final int beforeNewLocation = newLocation - 1; - // check surroundings of this location for adjacent text nodes depending on the - // kind of update, first the one with higher PRE values (due to shifts!) - // ... for insert/replace ... - if(insseq != null) { - // calculate the current following node - final int followingNode = newLocation + insseq.size(); - final int beforeFollowingNode = followingNode - 1; - // check the nodes at the end of/after the insertion sequence - if(beforeFollowingNode < smallestVisited) { - final Delete del = mergeTextNodes(beforeFollowingNode); - if(del != null) deletes.add(0, del); - smallestVisited = beforeFollowingNode; - } - } - // check nodes for delete and for insert before the updated location - if(beforeNewLocation < smallestVisited) { - final Delete del = mergeTextNodes(beforeNewLocation); - if(del != null) deletes.add(0, del); - smallestVisited = beforeNewLocation; - } - } - - final AtomicUpdateCache auc = new AtomicUpdateCache(data); - for(final Delete delete : deletes) auc.considerAtomic(delete, true); - deletes.clear(); - auc.applyUpdates(); - auc.adjustDistances(); - auc.clear(); - } - - /** - * Returns atomic text node merging operations if necessary for the given node PRE and - * its right neighbor PRE+1. - * @param pre node PRE value - * @return list of text merging operations - */ - private Delete mergeTextNodes(final int pre) { - final int s = data.meta.size; - final int b = pre + 1; - // don't leave table - if(pre >= s || b >= s || pre < 0 || b < 0) return null; - // only merge texts - if(data.kind(pre) != Data.TEXT || data.kind(b) != Data.TEXT) return null; - // only merge neighboring texts - if(data.parent(pre, Data.TEXT) != data.parent(b, Data.TEXT)) return null; - - // apply text node updates on the fly and throw them away - UpdateValue.getInstance(data, pre, Token.concat(data.text(pre, true), - data.text(b, true))). - apply(data); - // deletes must be cached to add them front-to-back to atomic update list - return Delete.getInstance(data, b); - } -} \ No newline at end of file diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/BasicUpdate.java basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/BasicUpdate.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/BasicUpdate.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/BasicUpdate.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,70 +0,0 @@ -package org.basex.data.atomic; - -import org.basex.data.*; - -/** - * Abstract atomic update. - * Atomic updates can only be initialized via {@link AtomicUpdateCache}. - * - * @author BaseX Team 2005-15, BSD License - * @author Lukas Kircher - */ -public abstract class BasicUpdate { - /** PRE value of the target location. */ - final int location; - /** Parent PRE of nodes to insert. */ - final int parent; - - /** - * Constructor. - * @param location target node location PRE - * @param parent parent node PRE value - */ - BasicUpdate(final int location, final int parent) { - this.location = location; - this.parent = parent; - } - - /** - * Getter for accumulated shifts. - * @return accumulated shifts, or zero if non-structural update. - */ - int accumulatedShifts() { - return 0; - } - - /** - * Applies the update to the given data instance. - * @param data data instance on which to execute the update - */ - abstract void apply(final Data data); - - /** - * Returns the data to be inserted (for inserts,...). - * @return Insertion sequence data instance - */ - abstract DataClip getInsertionData(); - - /** - * Returns whether this updates destroys the target nodes identity. Used to determine - * superfluous operations on the subtree of the target. - * @return {@code true} if target node identity destroyed - */ - abstract boolean destructive(); - - /** - * Merges the given update and this update if possible. - * @param data data reference - * @param bu update to merge with - * @return merged atomic update or null if merge not possible - */ - @SuppressWarnings("unused") - public BasicUpdate merge(final Data data, final BasicUpdate bu) { - return null; - } - - @Override - public String toString() { - return "L" + location; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/DataClip.java basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/DataClip.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/DataClip.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/DataClip.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,52 +0,0 @@ -package org.basex.data.atomic; - -import org.basex.data.*; - -/** - * Data container with start and end offset. Used mostly to save memory with insertion - * sequence caching (only one {@link Data} instance). - * - * Arbitrary trees can be stored in this clip. To distinguish between two insertion - * sequences, the root node of each sequence points to the parent PRE==-1. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -public final class DataClip { - /** Data reference. */ - public final Data data; - /** Start value. */ - public final int start; - /** End value (+1). */ - public final int end; - /** Number of contained fragments. */ - public int fragments; - - /** - * Constructor. - * @param data data reference - */ - public DataClip(final Data data) { - this(data, 0, data.meta.size); - } - - /** - * Constructor. - * @param data data reference - * @param start start - * @param end end - */ - public DataClip(final Data data, final int start, final int end) { - this.data = data; - this.start = start; - this.end = end; - } - - /** - * Returns the box size. - * @return size - */ - public int size() { - return end - start; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/Delete.java basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/Delete.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/Delete.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/Delete.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,67 +0,0 @@ -package org.basex.data.atomic; - -import org.basex.data.*; - -/** - * Atomic update operation that deletes a node. - * - * @author BaseX Team 2005-15, BSD License - * @author Lukas Kircher - */ -final class Delete extends StructuralUpdate { - /** - * Constructor. - * @param location target node location PRE - * @param shifts PRE value shifts introduced by update - * @param acc accumulated shifts - * @param first PRE value of the first node which distance has to be updated - * @param parent parent - */ - private Delete(final int location, final int shifts, final int acc, final int first, - final int parent) { - super(location, shifts, acc, first, parent); - } - - /** - * Factory. - * @param data data reference - * @param pre location pre value - * @return atomic delete - */ - static Delete getInstance(final Data data, final int pre) { - final int k = data.kind(pre); - final int s = data.size(pre, k); - return new Delete(pre, -s, -s, pre + s, data.parent(pre, k)); - } - - @Override - void apply(final Data data) { - data.delete(location); - } - - @Override - DataClip getInsertionData() { - return null; - } - - @Override - boolean destructive() { - return true; - } - - @Override - public String toString() { - return "\n Delete: " + super.toString(); - } - - @Override - public BasicUpdate merge(final Data data, final BasicUpdate bu) { - if(bu != null && parent == bu.parent && bu instanceof Insert && - location - shifts == bu.location && data.kind(location) != Data.ATTR) { - final Insert ins = (Insert) bu; - return new Replace(location, shifts + ins.shifts, - ins.accumulatedShifts, preOfAffectedNode, ins.clip, parent); - } - return null; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/InsertAttr.java basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/InsertAttr.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/InsertAttr.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/InsertAttr.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,61 +0,0 @@ -package org.basex.data.atomic; - -import org.basex.data.*; - -/** - * Atomic update operation that inserts an attribute into a database. - * - * @author BaseX Team 2005-15, BSD License - * @author Lukas Kircher - */ -final class InsertAttr extends StructuralUpdate { - /** Insertion sequence. */ - private final DataClip clip; - - /** - * Constructor. - * @param location PRE value of the target node location - * @param shifts row shifts - * @param acc accumulated row shifts - * @param first PRE value of the first node which distance has to be updated - * @param parent parent PRE value for the inserted node - * @param clip insert sequence data clip - */ - private InsertAttr(final int location, final int shifts, final int acc, final int first, - final int parent, final DataClip clip) { - super(location, shifts, acc, first, parent); - this.clip = clip; - } - - /** - * Factory. - * @param pre target location PRE - * @param par parent of new attribute - * @param clip insertion sequence - * @return instance - */ - static InsertAttr getInstance(final int pre, final int par, final DataClip clip) { - final int sh = clip.size(); - return new InsertAttr(pre, sh, sh, pre, par, clip); - } - - @Override - void apply(final Data data) { - data.insertAttr(location, parent, clip); - } - - @Override - DataClip getInsertionData() { - return clip; - } - - @Override - boolean destructive() { - return false; - } - - @Override - public String toString() { - return "\nInsertAttr: " + super.toString(); - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/Insert.java basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/Insert.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/Insert.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/Insert.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -package org.basex.data.atomic; - -import org.basex.data.*; - -/** - * Atomic insert that inserts a given insertion sequence data instance into a database. - * - * @author BaseX Team 2005-15, BSD License - * @author Lukas Kircher - */ -final class Insert extends StructuralUpdate { - /** Insertion sequence. */ - final DataClip clip; - - /** - * Constructor. - * @param location PRE value of the target node location - * @param shifts shifts - * @param acc accumulated shifts - * @param first PRE value of the first node which distance has to be updated - * @param parent parent PRE value for the inserted nodes - * @param clip insertion sequence data clip - */ - private Insert(final int location, final int shifts, final int acc, final int first, - final int parent, final DataClip clip) { - super(location, shifts, acc, first, parent); - this.clip = clip; - } - - /** - * Factory. - * @param pre target location PRE - * @param par parent of inserted node - * @param clip insertion sequence - * @return instance - */ - static Insert getInstance(final int pre, final int par, final DataClip clip) { - final int s = clip.size(); - return new Insert(pre, s, s, pre, par, clip); - } - - @Override - void apply(final Data data) { - data.insert(location, parent, clip); - } - - @Override - DataClip getInsertionData() { - return clip; - } - - @Override - boolean destructive() { - return false; - } - - @Override - public BasicUpdate merge(final Data data, final BasicUpdate bu) { - if(bu != null && parent == bu.parent && bu instanceof Delete && location == bu.location - && data.kind(bu.location) != Data.ATTR) { - final Delete del = (Delete) bu; - return new Replace(location, shifts + del.shifts, - del.accumulatedShifts, del.preOfAffectedNode, clip, parent); - } - return null; - } - - @Override - public String toString() { - return "\n Insert: " + super.toString(); - } -} \ No newline at end of file diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/Rename.java basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/Rename.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/Rename.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/Rename.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,68 +0,0 @@ -package org.basex.data.atomic; - -import org.basex.data.*; -import org.basex.util.*; - -/** - * Atomic update operation that renames a node. - * - * @author BaseX Team 2005-15, BSD License - * @author Lukas Kircher - */ -final class Rename extends BasicUpdate { - /** Kind of updated node. */ - private final int kind; - /** The new name of the node. */ - private final byte[] name; - /** Name URI. */ - private final byte[] uri; - - /** - * Constructor. - * @param location PRE value of target node location - * @param kind target node kind - * @param name new name for the target node - * @param uri new name uri for the target node - * @param parent parent node PRE - */ - private Rename(final int location, final int kind, final byte[] name, final byte[] uri, - final int parent) { - super(location, parent); - if(name.length == 0) throw Util.notExpected("New name must not be empty."); - this.kind = kind; - this.name = name; - this.uri = uri; - } - - /** - * Factory. - * @param data data reference - * @param pre target node PRE - * @param name new name - * @param uri new uri - * @return instance - */ - static Rename getInstance(final Data data, final int pre, final byte[] name, final byte[] uri) { - return new Rename(pre, data.kind(pre), name, uri, data.parent(pre, data.kind(pre))); - } - - @Override - void apply(final Data data) { - data.update(location, kind, name, uri); - } - - @Override - DataClip getInsertionData() { - throw Util.notExpected("No insertion sequence needed for atomic rename operation."); - } - - @Override - boolean destructive() { - return false; - } - - @Override - public String toString() { - return "\n Rename: " + super.toString(); - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/Replace.java basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/Replace.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/Replace.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/Replace.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,150 +0,0 @@ -package org.basex.data.atomic; - -import static org.basex.util.Token.*; - -import java.util.*; - -import org.basex.data.*; - -/** - * Replaces a node in the database with an insertion sequence. - * - * @author BaseX Team 2005-15, BSD License - * @author Lukas Kircher - */ -final class Replace extends StructuralUpdate { - /** Insertion sequence. */ - private final DataClip clip; - - /** - * Constructor. - * @param location PRE value of the target node location - * @param clip insertion sequence data clip - * @param shifts PRE value shifts introduced by update - * @param acc accumulated shifts - * @param first PRE value of the first node which distance has to be updated - * @param parent parent node PRE - */ - Replace(final int location, final int shifts, final int acc, final int first, final DataClip clip, - final int parent) { - super(location, shifts, acc, first, parent); - this.clip = clip; - } - - /** - * Factory. - * @param data data reference - * @param pre target node PRE - * @param clip insertion sequence - * @return instance - */ - static Replace getInstance(final Data data, final int pre, final DataClip clip) { - final int kind = data.kind(pre); - final int par = data.parent(pre, kind); - final int oldsize = data.size(pre, kind); - final int newsize = clip.size(); - final int sh = newsize - oldsize; - return new Replace(pre, sh, sh, pre + oldsize, clip, par); - } - - @Override - void apply(final Data data) { - // [LK] replace optimizations only work without namespaces.. - if(data.nspaces.size() == 0 && clip.data.nspaces.size() == 0) { - // Lazy Replace: rewrite to value updates if structure has not changed - if(lazyReplace(data)) return; - // Rapid Replace: in-place update, overwrite existing table entries - data.replace(location, clip); - } else { - // fallback: delete old entries, add new ones - final int kind = data.kind(location); - final int par = data.parent(location, kind); - // delete first - otherwise insert must be at location+1 - data.delete(location); - if(kind == Data.ATTR) { - data.insertAttr(location, par, clip); - } else { - data.insert(location, par, clip); - } - } - } - - /** - * Lazy Replace implementation. Checks if the replace operation can be substituted with - * cheaper value updates. If structural changes have to be made no substitution takes place. - * @param data destination data reference - * @return true if substitution successful - */ - private boolean lazyReplace(final Data data) { - final Data src = clip.data; - final int srcSize = clip.size(); - // check for equal subtree size - if(srcSize != data.size(location, data.kind(location))) return false; - - final List valueUpdates = new ArrayList<>(); - for(int c = 0; c < srcSize; c++) { - final int s = clip.start + c; - final int t = location + c; - final int sk = src.kind(s); - final int tk = data.kind(t); - - if(sk != tk) - return false; - // distance can differ for first two tuples - if(c > 0 && src.dist(s, sk) != data.dist(t, tk)) - return false; - // check texts, comments and documents - if(sk == Data.TEXT || sk == Data.COMM || sk == Data.DOC) { - final byte[] srcText = src.text(s, true); - if(!eq(data.text(t, true), srcText)) - valueUpdates.add(UpdateValue.getInstance(data, t, srcText)); - } else { - // check elements, attributes and processing instructions - final byte[] srcName = src.name(s, sk); - final byte[] trgName = data.name(t, tk); - if(!eq(srcName, trgName)) valueUpdates.add(Rename.getInstance(data, t, srcName, EMPTY)); - switch(sk) { - case Data.ELEM: - // check size of elements - if(src.attSize(s, sk) != data.attSize(t, tk) || src.size(s, sk) != data.size(t, tk)) - return false; - break; - case Data.ATTR: - // check attribute values - final byte[] av = src.text(s, false); - if(!eq(data.text(t, false), av)) - valueUpdates.add(UpdateValue.getInstance(data, t, av)); - break; - case Data.PI: - // check processing instruction value - final byte[] srcText = src.text(s, true); - final byte[] trgText = data.text(t, true); - final int i = indexOf(srcText, ' '); - final byte[] pv = i == -1 ? EMPTY : substring(srcText, i + 1); - if(!eq(pv, indexOf(trgText, ' ') == -1 ? EMPTY : - substring(trgText, i + 1))) { - valueUpdates.add(UpdateValue.getInstance(data, t, pv)); - } - break; - } - } - } - for(final BasicUpdate bu : valueUpdates) bu.apply(data); - return true; - } - - @Override - DataClip getInsertionData() { - return clip; - } - - @Override - boolean destructive() { - return true; - } - - @Override - public String toString() { - return "\nReplace: " + super.toString(); - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/StructuralUpdate.java basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/StructuralUpdate.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/StructuralUpdate.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/StructuralUpdate.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,48 +0,0 @@ -package org.basex.data.atomic; - -/** - * Base class for structural updates that add to/remove from the table and introduce shifts. - * @author BaseX Team 2005-15, BSD License - * @author Lukas Kircher - */ -abstract class StructuralUpdate extends BasicUpdate { - /** PRE value shifts introduced by this atomic update due to structural changes. */ - final int shifts; - /** PRE value of the first node for which the distance must be updated due to PRE value - * shifts introduced by this update. */ - final int preOfAffectedNode; - /** Total/accumulated number of shifts introduced by all updates on the list up to this - * update (inclusive). The number of total shifts is used to calculate PRE values - * before/after updates. */ - int accumulatedShifts; - - /** - * Constructor. - * @param location target node location PRE - * @param shifts PRE value shifts introduced by update - * @param acc accumulated shifts - * @param first PRE value of the first node the distance of which has to be updated - * @param parent parent node - */ - StructuralUpdate(final int location, final int shifts, final int acc, final int first, - final int parent) { - super(location, parent); - this.shifts = shifts; - accumulatedShifts = acc; - preOfAffectedNode = first; - } - - @Override - int accumulatedShifts() { - return accumulatedShifts; - } - - @Override - public String toString() { - return "L" + location + - " PAR" + parent + - " SHF" + shifts + - " ASHF" + accumulatedShifts + - " AFF" + preOfAffectedNode; - } -} \ No newline at end of file diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/UpdateValue.java basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/UpdateValue.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/atomic/UpdateValue.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/atomic/UpdateValue.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,62 +0,0 @@ -package org.basex.data.atomic; - -import org.basex.data.*; -import org.basex.util.*; - -/** - * Atomic update operation that replaces the value of a single text, comment, pi or - * attribute node. - * - * @author BaseX Team 2005-15, BSD License - * @author Lukas Kircher - */ -final class UpdateValue extends BasicUpdate { - /** Target node kind. */ - private final int kind; - /** New value for target node. */ - private final byte[] value; - - /** - * Constructor. - * @param location PRE value of the target node location - * @param kind node kind of the target node - * @param value new value which is assigned to the target node - * @param parent parent of updated node - */ - private UpdateValue(final int location, final int kind, final byte[] value, final int parent) { - super(location, parent); - this.kind = kind; - this.value = value; - } - - /** - * Factory. - * @param data data reference - * @param pre PRE value of the target node location - * @param value new value which is assigned to the target node - * @return new instance - */ - static UpdateValue getInstance(final Data data, final int pre, final byte[] value) { - return new UpdateValue(pre, data.kind(pre), value, data.parent(pre, data.kind(pre))); - } - - @Override - void apply(final Data data) { - data.update(location, kind, value); - } - - @Override - DataClip getInsertionData() { - throw Util.notExpected("No insertion sequence needed for atomic value update operation."); - } - - @Override - boolean destructive() { - return false; - } - - @Override - public String toString() { - return "\nUpdateValue: " + super.toString(); - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/DataClip.java basex-8.2.3/basex-core/src/main/java/org/basex/data/DataClip.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/DataClip.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/DataClip.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,62 @@ +package org.basex.data; + +/** + * Data container with start and end offset. Used mostly to save memory with insertion + * sequence caching (only one {@link Data} instance). + * + * Arbitrary trees can be stored in this clip. To distinguish between two insertion + * sequences, the root node of each sequence points to the virtual parent pre value -1. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class DataClip { + /** Data reference. */ + public final Data data; + /** Start value. */ + public final int start; + /** End value (+1). */ + public final int end; + /** Number of contained fragments. */ + public final int fragments; + + /** + * Constructor. + * @param data data reference + */ + public DataClip(final Data data) { + this(data, 0, data.meta.size); + } + + /** + * Constructor. + * @param data data reference + * @param start start + * @param end end + */ + public DataClip(final Data data, final int start, final int end) { + this(data, start, end, 1); + } + + /** + * Constructor. + * @param data data reference + * @param start start + * @param end end + * @param fragments number of fragments + */ + public DataClip(final Data data, final int start, final int end, final int fragments) { + this.data = data; + this.start = start; + this.end = end; + this.fragments = fragments; + } + + /** + * Returns the box size. + * @return size + */ + public int size() { + return end - start; + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/Data.java basex-8.2.3/basex-core/src/main/java/org/basex/data/Data.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/Data.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/Data.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,20 +4,19 @@ import java.io.*; import java.util.*; -import java.util.List; import org.basex.core.*; import org.basex.core.cmd.*; -import org.basex.data.atomic.*; import org.basex.index.*; import org.basex.index.name.*; import org.basex.index.path.*; import org.basex.index.query.*; import org.basex.index.resource.*; +import org.basex.index.value.*; import org.basex.io.*; import org.basex.io.random.*; +import org.basex.query.*; import org.basex.util.*; -import org.basex.util.hash.*; import org.basex.util.list.*; /** @@ -96,11 +95,11 @@ /** Path summary index. */ public PathSummary paths; /** Text index. */ - public Index textIndex; + public ValueIndex textIndex; /** Attribute value index. */ - public Index attrIndex; + public ValueIndex attrIndex; /** Full-text index instance. */ - public Index ftxtIndex; + public ValueIndex ftxtIndex; /** Table access file. */ TableAccess table; @@ -373,16 +372,16 @@ public final byte[] attValue(final int att, final int pre) { final int a = pre + attSize(pre, kind(pre)); int p = pre; - while(++p != a) if(name(p) == att) return text(p, false); + while(++p != a) if(nameId(p) == att) return text(p, false); return null; } /** - * Returns a reference to the name of an element, attribute or processing instruction. + * Returns the id of the name of an element, attribute or processing instruction. * @param pre pre value - * @return token reference + * @return name id */ - public final int name(final int pre) { + public final int nameId(final int pre) { return table.read2(pre, 1) & 0x7FFF; } @@ -398,18 +397,38 @@ final int i = indexOf(name, ' '); return i == -1 ? name : substring(name, 0, i); } - return (kind == ELEM ? elemNames : attrNames).key(name(pre)); + return (kind == ELEM ? elemNames : attrNames).key(nameId(pre)); } /** - * Returns a reference to the namespace of the addressed element or attribute. + * Returns the id of the namespace uri of the addressed element or attribute. * @param pre pre value * @param kind node kind - * @return namespace URI + * @return id of the namespace uri */ - public final int uri(final int pre, final int kind) { - return kind == ELEM || kind == ATTR ? - table.read1(pre, kind == ELEM ? 3 : 11) & 0xFF : 0; + public final int uriId(final int pre, final int kind) { + return kind == ELEM || kind == ATTR ? table.read1(pre, kind == ELEM ? 3 : 11) & 0xFF : 0; + } + + /** + * Returns the name and namespace uri of the addressed element or attribute. + * @param pre pre value + * @param kind node kind + * @return array with name and namespace uri + */ + public final byte[][] qname(final int pre, final int kind) { + final byte[] name = name(pre, kind); + byte[] uri = null; + final boolean hasPrefix = indexOf(name, ':') != -1; + if(hasPrefix || !nspaces.isEmpty()) { + final int uriId = uriId(pre, kind); + if(uriId > 0) { + uri = nspaces.uri(uriId); + } else if(hasPrefix && eq(prefix(name), XML)) { + uri = QueryText.XML_URI; + } + } + return new byte[][] { name, uri == null ? EMPTY : uri }; } /** @@ -422,21 +441,13 @@ } /** - * Returns all namespace keys and values. + * Returns all namespace prefixes and uris that are defined for the specified pre value. * Should only be called for element nodes. * @param pre pre value * @return key and value ids */ - public final Atts ns(final int pre) { - final Atts as = new Atts(); - if(nsFlag(pre)) { - final int[] nsp = nspaces.get(pre, this); - final int nl = nsp.length; - for(int n = 0; n < nl; n += 2) { - as.add(nspaces.prefix(nsp[n]), nspaces.uri(nsp[n + 1])); - } - } - return as; + public final Atts namespaces(final int pre) { + return nsFlag(pre) ? nspaces.values(pre, this) : new Atts(); } /** @@ -486,10 +497,10 @@ /** * Updates (renames) the name of an element, attribute or processing instruction. - * @param pre pre value - * @param kind node kind - * @param name name of new element, attribute or processing instruction - * @param uri uri + * @param pre pre value of the node to be updated + * @param kind node kind of the updated node + * @param name name of the new element, attribute or processing instruction + * @param uri namespace uri */ public final void update(final int pre, final int kind, final byte[] name, final byte[] uri) { meta.update(); @@ -497,179 +508,179 @@ if(kind == PI) { updateText(pre, trim(concat(name, SPACE, atom(pre))), PI); } else { - // update/set namespace reference - final int ouri = nspaces.uri(name, pre, this); - final boolean ne = ouri == 0 && uri.length != 0; - final int npre = kind == ATTR ? parent(pre, kind) : pre; - final int nuri = ne ? nspaces.add(npre, npre, prefix(name), uri, this) : - ouri != 0 && eq(nspaces.uri(ouri), uri) ? ouri : 0; - - // write namespace uri reference - table.write1(pre, kind == ELEM ? 3 : 11, nuri); - // write name reference - table.write2(pre, 1, (nsFlag(pre) ? 1 << 15 : 0) | - (kind == ELEM ? elemNames : attrNames).index(name, null, false)); - // write namespace flag - table.write2(npre, 1, (ne || nsFlag(npre) ? 1 << 15 : 0) | name(npre)); + // check if namespace has changed + final byte[] prefix = prefix(name); + final int oldUriId = nspaces.uriIdForPrefix(prefix, pre, this); + final boolean nsFlag = oldUriId == 0 && uri.length != 0 && !eq(prefix, XML); + final int nsPre = kind == ATTR ? parent(pre, kind) : pre; + final int uriId = nsFlag ? nspaces.add(nsPre, prefix, uri, this) : + oldUriId != 0 && eq(nspaces.uri(oldUriId), uri) ? oldUriId : 0; + + // write ids of namespace uri and name, and namespace flag + if(kind == ATTR) { + table.write1(pre, 11, uriId); + table.write2(pre, 1, attrNames.index(name, null, false)); + if(nsFlag) table.write2(nsPre, 1, 1 << 15 | nameId(nsPre)); + } else { + table.write1(pre, 3, uriId); + final int nameId = elemNames.index(name, null, false); + table.write2(nsPre, 1, (nsFlag || nsFlag(nsPre) ? 1 << 15 : 0) | nameId); + } } } /** - * Updates (replaces) the value of a single document, text, comment, pi or - * attribute node. - * @param pre pre value to be replaced + * Updates (replaces) the value of a single text, comment, pi, attribute or document node. + * @param pre pre value of the node to be updated * @param kind node kind - * @param value value to be updated (element name, text, comment, pi, document) + * @param value value to be updated (text, comment, pi, attribute, document name) */ public final void update(final int pre, final int kind, final byte[] value) { - final byte[] v = kind == PI ? trim(concat(name(pre, kind), SPACE, value)) : value; - if(eq(v, text(pre, kind != ATTR))) return; + final byte[] val = kind == PI ? trim(concat(name(pre, kind), SPACE, value)) : value; + if(eq(val, text(pre, kind != ATTR))) return; meta.update(); - updateText(pre, v, kind); + updateText(pre, val, kind); if(kind == DOC) resources.rename(pre, value); } /** * Rapid Replace implementation. Replaces parts of the database with the specified data instance. - * @param tpre pre value of target node to be replaced + * @param pre pre value of the node to be replaced * @param source clip with source data */ - public final void replace(final int tpre, final DataClip source) { + public final void replace(final int pre, final DataClip source) { meta.update(); - final int size = source.size(); - final Data data = source.data; - - final int tkind = kind(tpre); - final int tsize = size(tpre, tkind); - final int tpar = parent(tpre, tkind); - final int diff = size - tsize; - buffer(size); - resources.replace(tpre, tsize, source); + final int sCount = source.size(); + final int tKind = kind(pre); + final int tSize = size(pre, tKind); + final int tPar = parent(pre, tKind); + bufferSize(sCount); + resources.replace(pre, tSize, source); + // initialize update of updatable index structures if(meta.updindex) { - // update index - indexDelete(tpre, tsize); + indexDelete(pre, tSize); indexBegin(); } + final Data sData = source.data; int sTopPre = source.start; - for(int spre = sTopPre; spre < source.end; ++spre) { - final int kind = data.kind(spre); - // size and parent value of source node - final int ssize = data.size(spre, kind); - final int spar = data.parent(spre, kind); - final int pre = tpre + spre - source.start; + for(int sPre = source.start; sPre < source.end; ++sPre) { + // properties of the source node + final int sKind = sData.kind(sPre); + final int sSize = sData.size(sPre, sKind); + final int sPar = sData.parent(sPre, sKind); + final int cPre = pre + sPre - source.start; // calculate new distance value - final int dist; - if(spre == sTopPre) { + final int cDist; + if(sPre == sTopPre) { // handle top level entry: calculate distance based on target database - dist = pre - tpar; + cDist = cPre - tPar; // calculate pre value of next top level entry - sTopPre += ssize; + sTopPre += sSize; } else { - dist = spre - spar; + cDist = sPre - sPar; } - switch(kind) { + switch(sKind) { case DOC: // add document - doc(pre, ssize, data.text(spre, true)); + doc(cPre, sSize, sData.text(sPre, true)); ++meta.ndocs; break; case ELEM: // add element - final byte[] en = data.name(spre, kind); - elem(dist, elemNames.index(en, null, false), data.attSize(spre, kind), ssize, - nspaces.uri(en, true), false); + final byte[] en = sData.name(sPre, sKind); + elem(cDist, elemNames.index(en, null, false), sData.attSize(sPre, sKind), sSize, + nspaces.uriIdForPrefix(prefix(en), true), false); break; case TEXT: case COMM: case PI: // add text - text(pre, dist, data.text(spre, true), kind); + text(cPre, cDist, sData.text(sPre, true), sKind); break; case ATTR: // add attribute - final byte[] an = data.name(spre, kind); - attr(pre, dist, attrNames.index(an, null, false), data.text(spre, false), - nspaces.uri(an, false), false); + final byte[] an = sData.name(sPre, sKind); + attr(cPre, cDist, attrNames.index(an, null, false), sData.text(sPre, false), + nspaces.uriIdForPrefix(prefix(an), false)); break; } } + // update ID -> PRE map and index structures if(meta.updindex) { + idmap.delete(pre, id(pre), -tSize); + idmap.insert(pre, meta.lastid - sCount + 1, sCount); indexAdd(); - // update ID -> PRE map: - idmap.delete(tpre, id(tpre), -tsize); - idmap.insert(tpre, meta.lastid - size + 1, size); } - // update table: - table.replace(tpre, buffer(), tsize); - buffer(1); - - // no distance/size update if the two subtrees are of equal size - if(diff == 0) return; - - // increase/decrease size of ancestors, adjust distances of siblings - int p = tpar; - while(p >= 0) { - final int k = kind(p); - size(p, k, size(p, k) + diff); - p = parent(p, k); + // replace table entries, reset buffer size + table.replace(pre, buffer(), tSize); + bufferSize(1); + + // if necessary, increase/decrease size of ancestors and adjust distances of siblings + final int diff = sCount - tSize; + if(diff != 0) { + int p = tPar; + while(p >= 0) { + final int k = kind(p); + size(p, k, size(p, k) + diff); + p = parent(p, k); + } + if(!cache) updateDist(pre + sCount, diff); } - if(!cache) updateDist(tpre + size, diff); - // adjust attribute size of parent if attributes inserted. attribute size - // of parent cannot be reduced via a replace expression. - int spre = source.start; - if(data.kind(spre) == ATTR) { + // of parent cannot be decreased via a replace expression. + int sPre = source.start; + if(sData.kind(sPre) == ATTR) { int d = 0; - while(spre < source.end && data.kind(spre++) == ATTR) d++; - if(d > 1) attSize(tpar, kind(tpar), d + attSize(tpar, ELEM) - 1); + while(sPre < source.end && sData.kind(sPre++) == ATTR) d++; + if(d > 1) attSize(tPar, kind(tPar), d + attSize(tPar, ELEM) - 1); } } /** * Deletes a node and its descendants. - * @param pre pre value of the node to delete + * @param pre pre value of the node to be deleted */ public final void delete(final int pre) { meta.update(); // delete references in document index - int k = kind(pre); - final int s = size(pre, k); - resources.delete(pre, s); + int kind = kind(pre); + final int size = size(pre, kind); + resources.delete(pre, size); // delete entries in value indexes - if(meta.updindex) indexDelete(pre, s); + if(meta.updindex) indexDelete(pre, size); /// delete text or attribute value in heap file - if(k != DOC && k != ELEM) delete(pre, k != ATTR); + if(kind != DOC && kind != ELEM) delete(pre, kind != ATTR); // reduce size of ancestors int par = pre; // check if we are an attribute (different size counters) - if(k == ATTR) { + if(kind == ATTR) { par = parent(par, ATTR); attSize(par, ELEM, attSize(par, ELEM) - 1); size(par, ELEM, size(par, ELEM) - 1); - k = kind(par); + kind = kind(par); } - // delete namespace nodes and propagate PRE value shifts (before node sizes are touched!) - nspaces.delete(pre, s, this); + // delete namespace nodes and propagate pre value shifts (before node sizes are touched!) + nspaces.delete(pre, size, this); // reduce size of ancestors - while(par > 0 && k != DOC) { - par = parent(par, k); - k = kind(par); - size(par, k, size(par, k) - s); + while(par > 0 && kind != DOC) { + par = parent(par, kind); + kind = kind(par); + size(par, kind, size(par, kind) - size); } // preserve empty root node @@ -677,169 +688,143 @@ if(meta.updindex) { // delete node and descendants from ID -> PRE map: - idmap.delete(pre, id(pre), -s); + idmap.delete(pre, id(pre), -size); } // delete node from table structure and reduce document size - table.delete(pre, s); + table.delete(pre, size); - if(!cache) updateDist(pre, -s); + if(!cache) updateDist(pre, -size); } /** - * Inserts attributes. - * @param pre pre value - * @param par parent of node + * Inserts standalone attributes (without root element). + * @param pre target pre value (insertion position) + * @param par target parent pre value of node * @param source clip with source data */ public final void insertAttr(final int pre, final int par, final DataClip source) { - insert(pre, par, source); + // #1168/2: store one by one (otherwise, namespace declarations may be added more than once) + for(int s = 0; s < source.fragments; s++) { + final int start = source.start + s; + insert(pre + s, par, new DataClip(source.data, start, start + 1)); + } attSize(par, ELEM, attSize(par, ELEM) + source.size()); } /** - * Inserts a data instance at the specified pre value. - * Note that the specified data instance must differ from this instance. - * @param tpre target pre value - * @param tpar target parent pre value of node ({@code -1} if document is added) + * Inserts a data instance at the specified pre value. Notes: + *
      + *
    • The data instance in the specified data clip must differ from this instance.
    • + *
    • Attributes must be inserted via {@link #insertAttr}.
    • + *
    + * @param pre target pre value (insertion position) + * @param par target parent pre value of node ({@code -1} if document is added) * @param source clip with source data */ - public final void insert(final int tpre, final int tpar, final DataClip source) { + public final void insert(final int pre, final int par, final DataClip source) { meta.update(); // update value and document indexes if(meta.updindex) indexBegin(); - resources.insert(tpre, source); + resources.insert(pre, source); - final int size = source.size(); - final int buf = Math.min(size, IO.BLOCKSIZE >> IO.NODEPOWER); // resize buffer to cache more entries - buffer(buf); - - // find all namespaces in scope to avoid duplicate declarations - final TokenMap nsScope = nspaces.scope(tpar, this); + final int sCount = source.size(); + final int bSize = Math.min(sCount, IO.BLOCKSIZE >> IO.NODEPOWER); + bufferSize(bSize); - // loop through all entries - final IntList preStack = new IntList(); - final NSNode nsRoot = nspaces.getCurrent(); - final IntList flagPres = new IntList(); - // track existing NSNodes - their PRE values have to be shifted after each tuple insertion - final List nsNodesShift = nspaces.getNSNodes(tpre); + // organize namespaces to avoid duplicate declarations + final NSScope nsScope = new NSScope(pre, this); // indicates if database only contains a dummy node - final Data data = source.data; + final Data sdata = source.data; int c = 0, sTopPre = source.start; - for(int spre = sTopPre; spre < source.end; ++spre, ++c) { - if(c != 0 && c % buf == 0) insert(tpre + c - buf); + for(int sPre = sTopPre; sPre < source.end; ++sPre, ++c) { + if(c != 0 && c % bSize == 0) insert(pre + c - bSize); - final int pre = tpre + c; - final int kind = data.kind(spre); - // size and parent value of source node - final int ssize = data.size(spre, kind); - final int spar = data.parent(spre, kind); - - // calculate new distance value - final int dist; - if(spre == sTopPre) { + // values of source node + final int sKind = sdata.kind(sPre); + final int sSize = sdata.size(sPre, sKind); + final int sPar = sdata.parent(sPre, sKind); + + // pre and dist value of new node + final int nPre = pre + c, nDist; + if(sPre == sTopPre) { // handle top level entry: calculate distance based on target database - dist = pre - tpar; + nDist = nPre - par; // calculate pre value of next top level entry - sTopPre += ssize; + sTopPre += sSize; } else { // handle descendant node: calculate distance based on source database - dist = spre - spar; + nDist = sPre - sPar; } - // documents: use -1 as namespace root - final int nsPre = kind == DOC ? -1 : pre - dist; - if(c == 0) nspaces.root(nsPre, this); - while(!preStack.isEmpty() && preStack.peek() > nsPre) nspaces.close(preStack.pop()); + final int nsPre = sKind == DOC ? -1 : nPre - nDist; + nsScope.loop(nsPre, c); - switch(kind) { + switch(sKind) { case DOC: // add document - nspaces.prepare(); - doc(pre, ssize, data.text(spre, true)); + nsScope.open(nPre); + doc(nPre, sSize, sdata.text(sPre, true)); ++meta.ndocs; - preStack.push(pre); break; - case ELEM: - // add element - nspaces.prepare(); - boolean ne = false; - if(data.nsFlag(spre)) { - final Atts at = data.ns(spre); - final int as = at.size(); - for(int a = 0; a < as; a++) { - // see if prefix has been declared/ is part of current ns scope - final byte[] old = nsScope.get(at.name(a)); - if(old == null || !eq(old, at.value(a))) { - nspaces.add(at.name(a), at.value(a), pre); - ne = true; - } - } - } - final byte[] en = data.name(spre, kind); - elem(dist, elemNames.index(en, null, false), data.attSize(spre, kind), ssize, - nspaces.uri(en, true), ne); - preStack.push(pre); + case ELEM: { + // add element. + final boolean nsFlag = nsScope.open(nPre, sdata.namespaces(sPre)); + final byte[] name = sdata.name(sPre, sKind); + elem(nDist, elemNames.index(name, null, false), sdata.attSize(sPre, sKind), sSize, + nspaces.uriIdForPrefix(prefix(name), true), nsFlag); break; + } case TEXT: case COMM: case PI: - // add text - text(pre, dist, data.text(spre, true), kind); + // add text, comment or processing instruction + text(nPre, nDist, sdata.text(sPre, true), sKind); break; - case ATTR: + case ATTR: { // add attribute - final byte[] an = data.name(spre, kind); - // check if prefix already in nsScope or not - final byte[] attPref = prefix(an); - if(data.nsFlag(spre) && nsScope.get(attPref) == null) { - // add declaration to parent node - nspaces.add(nsPre, preStack.isEmpty() ? -1 : preStack.peek(), attPref, - data.nspaces.uri(data.uri(spre, kind)), this); - // save pre value to set ns flag later for this node. can't be done - // here as direct table access would interfere with the buffer - flagPres.add(nsPre); + final byte[] name = sdata.name(sPre, sKind); + int uriId = sdata.uriId(sPre, sKind); + // extend namespace scope and write namespace flag if attribute has a new namespaces + if(uriId != 0) { + final byte[] prefix = prefix(name), uri = sdata.nspaces.uri(uriId); + uriId = nspaces.uriIdForPrefix(prefix, false); + if(uriId == 0 && !Token.eq(prefix, Token.XML)) { + uriId = nspaces.add(nsPre, prefix, uri, this); + table.write2(nsPre, 1, 1 << 15 | nameId(nsPre)); + } } - attr(pre, dist, attrNames.index(an, null, false), data.text(spre, false), - nspaces.uri(an, false), false); - break; + attr(nPre, nDist, attrNames.index(name, null, false), sdata.text(sPre, false), uriId); + } } - // propagate PRE value shifts to keep namespace structure valid - Namespaces.incrementPre(nsNodesShift, 1); + nsScope.shift(1); } // finalize and update namespace structure - while(!preStack.isEmpty()) nspaces.close(preStack.pop()); - nspaces.setCurrent(nsRoot); + nsScope.close(); - if(bp != 0) insert(tpre + c - 1 - (c - 1) % buf); - // reset buffer to old size - buffer(1); - - // set namespace flags - final int fs = flagPres.size(); - for(int f = 0; f < fs; f++) { - final int fl = flagPres.get(f); - table.write2(fl, 1, name(fl) | 1 << 15); - } + // write final entries and reset buffer + if(bp != 0) insert(pre + c - 1 - (c - 1) % bSize); + bufferSize(1); // increase size of ancestors - int p = tpar; - while(p >= 0) { - final int k = kind(p); - size(p, k, size(p, k) + size); - p = parent(p, k); + int cPre = par; + while(cPre >= 0) { + final int cKind = kind(cPre); + size(cPre, cKind, size(cPre, cKind) + sCount); + cPre = parent(cPre, cKind); } + // add entries to the ID -> PRE mapping if(meta.updindex) { - // add the entries to the ID -> PRE mapping: - idmap.insert(tpre, id(tpre), size); + idmap.insert(pre, id(pre), sCount); indexAdd(); } - if(!cache) updateDist(tpre + size, size); + // finally, update distances + if(!cache) updateDist(pre + sCount, sCount); } /** @@ -911,15 +896,14 @@ * Sets the namespace flag. * Should be only called for element nodes. * @param pre pre value - * @param ne namespace flag + * @param nsFlag namespace flag */ - public final void nsFlag(final int pre, final boolean ne) { - table.write1(pre, 1, table.read1(pre, 1) & 0x7F | (ne ? 0x80 : 0)); + public final void nsFlag(final int pre, final boolean nsFlag) { + table.write1(pre, 1, table.read1(pre, 1) & 0x7F | (nsFlag ? 0x80 : 0)); } /** - * Inserts the internal buffer to the storage - * without updating the table structure. + * Inserts the internal buffer to the storage without updating the table structure. * @param pre insert position */ public final void insert(final int pre) { @@ -944,7 +928,7 @@ * Sets the update buffer to a new size. * @param size number of table entries */ - private void buffer(final int size) { + private void bufferSize(final int size) { final int bs = size << IO.NODEPOWER; if(b.length != bs) b = new byte[bs]; } @@ -967,20 +951,20 @@ /** * Adds an element entry to the internal update buffer. * @param dist parent distance - * @param name element name index + * @param nameId id of element name * @param asize number of attributes * @param size node size - * @param uri namespace uri reference - * @param ne namespace flag + * @param uriId id of namespace uri + * @param nsFlag namespace flag */ - public final void elem(final int dist, final int name, final int asize, final int size, - final int uri, final boolean ne) { + public final void elem(final int dist, final int nameId, final int asize, final int size, + final int uriId, final boolean nsFlag) { // build and insert new entry final int i = newID(); - final int n = ne ? 1 << 7 : 0; + final int n = nsFlag ? 1 << 7 : 0; s(Math.min(IO.MAXATTS, asize) << 3 | ELEM); - s(n | (byte) (name >> 8)); s(name); s(uri); + s(n | (byte) (nameId >> 8)); s(nameId); s(uriId); s(dist >> 24); s(dist >> 16); s(dist >> 8); s(dist); s(size >> 24); s(size >> 16); s(size >> 8); s(size); s(i >> 24); s(i >> 16); s(i >> 8); s(i); @@ -1007,22 +991,20 @@ * Adds an attribute entry to the internal update buffer. * @param pre pre value * @param dist parent distance - * @param name attribute name + * @param nameId id of attribute name * @param value attribute value - * @param uri namespace uri reference - * @param ne namespace flag (only {@code true} if this is a stand-alone attribute) + * @param uriId id of namespace uri */ - public final void attr(final int pre, final int dist, final int name, final byte[] value, - final int uri, final boolean ne) { + public final void attr(final int pre, final int dist, final int nameId, final byte[] value, + final int uriId) { // add attribute to text storage final int i = newID(); final long v = index(pre, i, value, ATTR); - final int n = ne ? 1 << 7 : 0; s(Math.min(IO.MAXATTS, dist) << 3 | ATTR); - s(n | (byte) (name >> 8)); s(name); s(v >> 32); + s(nameId >> 8); s(nameId); s(v >> 32); s(v >> 24); s(v >> 16); s(v >> 8); s(v); - s(0); s(0); s(0); s(uri); + s(0); s(0); s(0); s(uriId); s(i >> 24); s(i >> 16); s(i >> 8); s(i); } @@ -1080,7 +1062,7 @@ void indexDelete() { } /** - * Delete a node and its descendants from the corresponding indexes. + * Deletes a node and its descendants from the corresponding indexes. * @param pre pre value of the node to delete * @param size number of descendants */ @@ -1101,7 +1083,7 @@ * @param end end pre value * @return table */ - private String toString(final int start, final int end) { + public String toString(final int start, final int end) { return string(InfoStorage.table(this, start, end)); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/DataText.java basex-8.2.3/basex-core/src/main/java/org/basex/data/DataText.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/DataText.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/DataText.java 2015-07-14 10:54:40.000000000 +0000 @@ -133,7 +133,7 @@ String DOCDECL3 = "\" standalone=\""; /** HTML. */ - String HTML = "html"; + byte[] HTML = token("html"); /** Doctype output. */ String DOCTYPE = "= 0; - } - - /** - * Returns the position of the specified node or the negative value - 1 of - * the position where it should have been found. - * @param pre pre value - * @return true if the node was found - */ - public int find(final int pre) { - sort(); - return Arrays.binarySearch(sorted, pre); - } - - /** - * Adds or removes the specified pre node. - * @param pre pre value - */ - public void toggle(final int pre) { - final int[] n = { pre }; - set(contains(pre) ? except(pres, n) : union(pres, n)); - } - - /** - * Merges the specified array with the existing pre nodes. - * @param pre pre value - */ - public void union(final int[] pre) { - set(union(pres, pre)); - } - - /** - * Returns full-text position data. - * @return position data - */ - public FTPosData ftpos() { - return ftpos; - } - - /** - * Merges two sorted integer arrays via union. - * Note that the input arrays must be sorted. - * @param pres1 first set - * @param pres2 second set - * @return resulting set - */ - private static int[] union(final int[] pres1, final int[] pres2) { - final int al = pres1.length, bl = pres2.length; - final IntList il = new IntList(); - int a = 0, b = 0; - while(a != al && b != bl) { - final int d = pres1[a] - pres2[b]; - il.add(d <= 0 ? pres1[a++] : pres2[b++]); - if(d == 0) ++b; - } - while(a != al) il.add(pres1[a++]); - while(b != bl) il.add(pres2[b++]); - return il.finish(); - } - - /** - * Subtracts the second from the first array. - * Note that the input arrays must be sorted. - * @param pres1 first set - * @param pres2 second set - * @return resulting set - */ - private static int[] except(final int[] pres1, final int[] pres2) { - final int al = pres1.length, bl = pres2.length; - final IntList il = new IntList(); - int a = 0, b = 0; - while(a != al && b != bl) { - final int d = pres1[a] - pres2[b]; - if(d < 0) il.add(pres1[a]); - else ++b; - if(d <= 0) ++a; - } - while(a != al) il.add(pres1[a++]); - return il.finish(); - } - - /** - * Sets the specified nodes. - * @param nodes values - */ - private void set(final int[] nodes) { - pres = nodes; - sorted = null; - } - - /** - * Creates a sorted node array. If the original array is already sorted, - * the same reference is used. - */ - private void sort() { - if(sorted != null) return; - int i = Integer.MIN_VALUE; - for(final int n : pres) { - if(i > n) { - sorted = Arrays.copyOf(pres, pres.length); - Arrays.sort(sorted); - return; - } - i = n; - } - sorted = pres; - } - - @Override - public void serialize(final Serializer ser) throws IOException { - final int pl = pres.length; - for(int pre = 0; pre < pl && !ser.finished(); pre++) serialize(ser, pre); - } - - @Override - public void serialize(final Serializer ser, final int pre) throws IOException { - ser.serialize(new FTPosNode(data, pres[pre], ftpos)); - } - - @Override - public String serialize() throws IOException { - final ArrayOutput ao = new ArrayOutput(); - serialize(Serializer.get(ao)); - return ao.toString(); - } - - @Override - public String toString() { - try { - return serialize(); - } catch(final IOException ex) { - throw Util.notExpected(ex); - } - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/DiskData.java basex-8.2.3/basex-core/src/main/java/org/basex/data/DiskData.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/DiskData.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/DiskData.java 2015-07-14 10:54:40.000000000 +0000 @@ -208,7 +208,7 @@ * @param type index to be opened * @param index index instance */ - private void set(final IndexType type, final Index index) { + private void set(final IndexType type, final ValueIndex index) { meta.dirty = true; switch(type) { case TEXT: textIndex = index; break; @@ -253,8 +253,8 @@ write(); texts.flush(); values.flush(); - if(textIndex != null) ((DiskValues) textIndex).flush(); - if(attrIndex != null) ((DiskValues) attrIndex).flush(); + if(textIndex != null) textIndex.flush(); + if(attrIndex != null) attrIndex.flush(); } } catch(final IOException ex) { Util.stack(ex); @@ -332,14 +332,9 @@ protected void updateText(final int pre, final byte[] value, final int kind) { final boolean text = kind != ATTR; - if(meta.updindex) { - // update indexes - final int id = id(pre); - final byte[] oldval = text(pre, text); - final DiskValues index = (DiskValues) (text ? textIndex : attrIndex); - // don't index document names - if(index != null && kind != DOC) index.replace(oldval, value, id); - } + // update index; do not index document names + final ValueIndex index = text ? textIndex : attrIndex; + if(index != null && kind != DOC) index.replace(text(pre, text), value, id(pre)); // reference to text store final DataAccess store = text ? texts : values; @@ -381,14 +376,14 @@ @Override protected void indexAdd() { - if(!txtBuffer.isEmpty()) ((DiskValues) textIndex).add(txtBuffer); - if(!atvBuffer.isEmpty()) ((DiskValues) attrIndex).add(atvBuffer); + if(!txtBuffer.isEmpty()) textIndex.add(txtBuffer); + if(!atvBuffer.isEmpty()) attrIndex.add(atvBuffer); } @Override void indexDelete() { - if(!txtBuffer.isEmpty()) ((DiskValues) textIndex).delete(txtBuffer); - if(!atvBuffer.isEmpty()) ((DiskValues) attrIndex).delete(atvBuffer); + if(!txtBuffer.isEmpty()) textIndex.delete(txtBuffer); + if(!atvBuffer.isEmpty()) attrIndex.delete(atvBuffer); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/FTMatches.java basex-8.2.3/basex-core/src/main/java/org/basex/data/FTMatches.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/FTMatches.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/FTMatches.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,130 +0,0 @@ -package org.basex.data; - -import java.util.*; - -import org.basex.util.*; -import org.basex.util.list.*; - -/** - * AllMatches full-text container, referencing several {@link FTMatch} instances. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -public final class FTMatches extends ElementList implements Iterable { - /** Full-text matches. */ - public FTMatch[] match = {}; - /** Position of a token in the query. */ - public int pos; - - /** - * Constructor. - */ - public FTMatches() { - } - - /** - * Constructor. - * @param pos query position - */ - public FTMatches(final int pos) { - this.pos = pos; - } - - /** - * Resets the match container. - * @param ps query position - */ - public void reset(final int ps) { - pos = ps; - size = 0; - } - - /** - * Adds a match entry. - * @param ps position - */ - public void or(final int ps) { - or(ps, ps); - } - - /** - * Adds a match entry. - * @param start start position - * @param end end position - */ - public void or(final int start, final int end) { - add(new FTMatch(1).add(new FTStringMatch(start, end, pos))); - } - - /** - * Adds a match entry. - * @param start start position - * @param end end position - */ - public void and(final int start, final int end) { - final FTStringMatch sm = new FTStringMatch(start, end, pos); - for(final FTMatch m : this) m.add(sm); - } - - /** - * Adds a match entry. - * @param ftm match to be added - */ - public void add(final FTMatch ftm) { - if(size == match.length) match = Array.copy(match, new FTMatch[Array.newSize(size)]); - match[size++] = ftm; - } - - /** - * Removes the specified match. - * @param index match index - */ - public void delete(final int index) { - Array.move(match, index + 1, -1, --size - index); - } - - /** - * Checks if at least one of the matches contains only includes. - * @return result of check - */ - public boolean matches() { - for(final FTMatch m : this) if(m.match()) return true; - return false; - } - - /** - * Combines two matches as phrase. - * @param all second match list - * @param distance word distance - * @return true if matches are left - */ - public boolean phrase(final FTMatches all, final int distance) { - int a = 0, b = 0, c = 0; - while(a < size && b < all.size) { - final int e = all.match[b].match[0].start; - final int d = e - match[a].match[0].end - distance; - if(d == 0) { - match[c] = match[a]; - match[c++].match[0].end = e; - } - if(d >= 0) ++a; - if(d <= 0) ++b; - } - size = c; - return size != 0; - } - - @Override - public Iterator iterator() { - return new ArrayIterator<>(match, size); - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(); - sb.append(Util.className(this)).append('[').append(pos).append(']'); - for(final FTMatch m : this) sb.append("\n ").append(m); - return sb.toString(); - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/FTMatch.java basex-8.2.3/basex-core/src/main/java/org/basex/data/FTMatch.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/FTMatch.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/FTMatch.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,95 +0,0 @@ -package org.basex.data; - -import java.util.*; - -import org.basex.util.*; -import org.basex.util.list.*; - -/** - * Match full-text container, referencing several {@link FTStringMatch} instances. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -public final class FTMatch extends ElementList implements Iterable { - /** String matches. */ - FTStringMatch[] match; - - /** - * Constructor. - */ - public FTMatch() { - this(0); - } - - /** - * Constructor, specifying an initial internal array size. - * @param capacity initial array capacity - */ - public FTMatch(final int capacity) { - match = new FTStringMatch[capacity]; - } - - /** - * Adds all matches of a full-text match. - * @param ftm match to be added - * @return self reference - */ - public FTMatch add(final FTMatch ftm) { - for(final FTStringMatch sm : ftm) add(sm); - return this; - } - - /** - * Adds a single string match. - * @param ftm match to be added - * @return self reference - */ - public FTMatch add(final FTStringMatch ftm) { - if(size == match.length) match = Array.copy(match, new FTStringMatch[newSize()]); - match[size++] = ftm; - return this; - } - - /** - * Checks if the full-text match is not part of the specified match. - * @param ftm match to be checked - * @return result of check - */ - public boolean notin(final FTMatch ftm) { - for(final FTStringMatch s : this) { - for(final FTStringMatch sm : ftm) if(!s.in(sm)) return true; - } - return false; - } - - /** - * Checks if the match contains no string excludes. - * @return result of check - */ - boolean match() { - for(final FTStringMatch s : this) if(s.exclude) return false; - return true; - } - - /** - * Sorts the matches by their start and end positions. - */ - public void sort() { - Arrays.sort(match, 0, size, null); - } - - @Override - public Iterator iterator() { - return new ArrayIterator<>(match, size); - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(); - for(final FTStringMatch s : this) { - sb.append(sb.length() == 0 ? "" : ", ").append(s); - } - return Util.className(this) + ' ' + sb; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/FTPosData.java basex-8.2.3/basex-core/src/main/java/org/basex/data/FTPosData.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/FTPosData.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/FTPosData.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,110 +0,0 @@ -package org.basex.data; - -import java.util.*; - -import org.basex.util.*; -import org.basex.util.hash.*; -import org.basex.util.list.*; - -/** - * This class provides a container for query full-text positions, - * which is evaluated by the visualizations. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - * @author Sebastian Gath - */ -public final class FTPosData { - /** Position references. */ - private FTPos[] pos = new FTPos[1]; - /** Data reference. */ - private Data dt; - /** Number of values. */ - private int size; - - /** - * Adds position data. - * - * @param data data reference - * @param pre pre value - * @param all full-text matches - */ - public void add(final Data data, final int pre, final FTMatches all) { - if(dt == null) dt = data; - else if(dt != data) return; - - // cache all positions - final IntSet set = new IntSet(); - for(final FTMatch ftm : all) { - for(final FTStringMatch sm : ftm) { - for(int s = sm.start; s <= sm.end; ++s) set.add(s); - } - } - - // sort and store all positions - final IntList il = new IntList(set.toArray()).sort(); - int c = find(pre); - if(c < 0) { - c = -c - 1; - if(size == pos.length) pos = Arrays.copyOf(pos, Array.newSize(size)); - Array.move(pos, c, 1, size++ - c); - pos[c] = new FTPos(pre, il); - } else { - pos[c].union(il); - } - } - - /** - * Gets full-text data from the container. - * If no data is stored for a pre value, {@code null} is returned. - * int[0] : [pos0, ..., posn] - * int[1] : [poi0, ..., poin] - * @param data data reference - * @param pre int pre value - * @return int[2][n] full-text data or {@code null} - */ - public FTPos get(final Data data, final int pre) { - final int p = find(pre); - return p < 0 || dt != data ? null : pos[p]; - } - - /** - * Returns the number of entries. - * @return size - */ - public int size() { - int c = 0; - for(int i = 0; i < size; ++i) c += pos[i].size(); - return c; - } - - @Override - public boolean equals(final Object obj) { - if(!(obj instanceof FTPosData)) return false; - final FTPosData ft = (FTPosData) obj; - if(size != ft.size) return false; - for(int i = 0; i < size; ++i) { - if(pos[i].pre != ft.pos[i].pre || !Arrays.equals( - pos[i].list.toArray(), ft.pos[i].list.toArray())) return false; - } - return true; - } - - /** - * Returns the index of the specified pre value. - * @param pre int pre value - * @return index, or negative index - 1 if pre value is not found - */ - private int find(final int pre) { - // binary search - int l = 0, h = size - 1; - while(l <= h) { - final int m = l + h >>> 1; - final int c = pos[m].pre - pre; - if(c == 0) return m; - if(c < 0) l = m + 1; - else h = m - 1; - } - return -l - 1; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/FTPos.java basex-8.2.3/basex-core/src/main/java/org/basex/data/FTPos.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/FTPos.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/FTPos.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,61 +0,0 @@ -package org.basex.data; - -import org.basex.util.hash.*; -import org.basex.util.list.*; - -/** - * This class contains full-text positions for a single database node. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -public final class FTPos { - /** Pre value. */ - final int pre; - /** Positions. */ - IntList list; - - /** - * Constructor. - * @param pre pre value - * @param list sorted positions - */ - FTPos(final int pre, final IntList list) { - this.pre = pre; - this.list = list; - } - - /** - * Merges the specified position arrays. - * @param pos sorted positions - */ - void union(final IntList pos) { - final int ps = list.size(), ls = pos.size(); - final IntSet set = new IntSet(ps + ls); - for(int p = 0, s = ps; p < s; p++) set.add(list.get(p)); - for(int l = 0, s = ls; l < s; l++) set.add(pos.get(l)); - list = new IntList(set.toArray()).sort(); - } - - /** - * Checks if the specified position is found. - * @param pos position to be found - * @return result of check - */ - public boolean contains(final int pos) { - return list.sortedIndexOf(pos) >= 0; - } - - /** - * Returns the number of positions. - * @return number of positions - */ - public int size() { - return list.size(); - } - - @Override - public String toString() { - return pre + ": " + list; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/FTStringMatch.java basex-8.2.3/basex-core/src/main/java/org/basex/data/FTStringMatch.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/FTStringMatch.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/FTStringMatch.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,67 +0,0 @@ -package org.basex.data; - -/** - * Single full-text string match. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -public final class FTStringMatch implements Comparable { - /** Position of the token in the query. */ - public final int pos; - /** Start position. */ - public final int start; - /** End position. */ - public int end; - /** Exclude flag. */ - public boolean exclude; - /** Gaps (non-contiguous) flag. */ - public boolean gaps; - - /** - * Constructor. - * @param start start position - * @param end end position - * @param pos query pos - */ - FTStringMatch(final int start, final int end, final int pos) { - this.start = start; - this.end = end; - this.pos = pos; - } - - /** - * Checks if the match is included in the specified match. - * @param mtc match to be compared - * @return result of check - */ - boolean in(final FTStringMatch mtc) { - return start >= mtc.start && end <= mtc.end; - } - - @Override - public boolean equals(final Object ftm) { - if(!(ftm instanceof FTStringMatch)) return false; - final FTStringMatch sm = (FTStringMatch) ftm; - return start == sm.start && end == sm.end; - } - - @Override - public int compareTo(final FTStringMatch sm) { - final int s = start - sm.start; - return s == 0 ? end - sm.end : s; - } - - @Override - public int hashCode() { - final int h = start + 1; - return (h << 5) - h + end; - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder().append(pos); - sb.append(':').append(start).append('-').append(end); - return exclude ? "not(" + sb + ')' : sb.toString(); - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/MemData.java basex-8.2.3/basex-core/src/main/java/org/basex/data/MemData.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/MemData.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/MemData.java 2015-07-14 10:54:40.000000000 +0000 @@ -38,7 +38,7 @@ * @param options database options */ private MemData(final Names elemNames, final Names attrNames, final PathSummary paths, - final Namespaces nspaces, final Index textIndex, final Index attrIndex, + final Namespaces nspaces, final ValueIndex textIndex, final ValueIndex attrIndex, final MainOptions options) { super(new MetaData(options)); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/Namespaces.java basex-8.2.3/basex-core/src/main/java/org/basex/data/Namespaces.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/Namespaces.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/Namespaces.java 2015-07-14 10:54:40.000000000 +0000 @@ -19,32 +19,30 @@ * @author Christian Gruen */ public final class Namespaces { - /** Stack with references to default namespaces. */ - private final IntList defaults = new IntList(2); /** Namespace prefixes. */ - private final TokenSet prefs; + private final TokenSet prefixes; /** Namespace URIs. */ private final TokenSet uris; /** Root node. */ private final NSNode root; - /** Indicates if new namespaces have been added for an XML node. */ - private boolean newns; + /** Stack with references to current default namespaces. */ + private final IntList defaults = new IntList(2); /** Current level. Index starts at 1 (required by XQUF operations). */ private int level = 1; /** Current namespace node. */ - private NSNode current; + private NSNode cursor; - // Building Namespaces ================================================================ + // Creating and Writing Namespaces ============================================================== /** * Empty constructor. */ public Namespaces() { - prefs = new TokenSet(); + prefixes = new TokenSet(); uris = new TokenSet(); root = new NSNode(-1); - current = root; + cursor = root; } /** @@ -53,10 +51,10 @@ * @throws IOException I/O exception */ Namespaces(final DataInput in) throws IOException { - prefs = new TokenSet(in); + prefixes = new TokenSet(in); uris = new TokenSet(in); root = new NSNode(in, null); - current = root; + cursor = root; } /** @@ -65,435 +63,353 @@ * @throws IOException I/O exception */ void write(final DataOutput out) throws IOException { - prefs.write(out); + prefixes.write(out); uris.write(out); root.write(out); } - /** - * Prepares the generation of new namespaces. - */ - public void prepare() { - final int nu = defaults.get(level); - defaults.set(++level, nu); - newns = false; - } + // Requesting Namespaces Globally =============================================================== /** - * Adds the specified namespace to the namespace structure of the current element. - * @param pref prefix - * @param uri uri - * @param pre pre value - * @return new NSNode if a new one has been created, {@code null} otherwise - */ - public NSNode add(final byte[] pref, final byte[] uri, final int pre) { - NSNode node = null; - if(!newns) { - node = new NSNode(pre); - current.add(node); - current = node; - newns = true; - } - final int k = prefs.put(pref); - final int v = uris.put(uri); - current.add(k, v); - if(pref.length == 0) defaults.set(level, v); - return node; - } - - /** - * Closes a namespace node. - * @param pre current pre value + * Returns if no namespaces exist. + * Note that the container size does not change if namespaces are deleted. + * This function is mainly used to decide namespaces need to be considered in query optimizations. + * @return result of check */ - public void close(final int pre) { - while(current.pr >= pre && current.parent != null) current = current.parent; - --level; + public boolean isEmpty() { + return uris.isEmpty(); } - // Requesting Namespaces ============================================================== - /** - * Returns the size of the uri container. - * Note that the container size does not change if namespaces are deleted. - * This function is basically used to decide if there are any namespaces at all, - * and if namespaces need to be considered in query optimizations. - * @return number of uri references + * Returns the number of namespaces that have been stored so far. + * @return number of entries */ public int size() { return uris.size(); } /** - * Returns the default namespace of the database or {@code null} - * if several (default or prefixed) namespaces are defined. - * @return global default namespace, or {@code null} + * Returns a prefix for the specified id. + * @param id id of prefix + * @return prefix */ - public byte[] globalNS() { - // no namespaces defined: default namespace is empty - if(root.sz == 0) return Token.EMPTY; - // more than one namespace defined: skip test - if(root.sz > 1) return null; - // check namespaces of first child - final NSNode n = root.children[0]; - - // namespace has more children; skip traversal - if(n.sz != 0 || n.pr != 1 || n.values.length != 2) return null; - // return default namespace or null - return prefix(n.values[0]).length == 0 ? uri(n.values[1]) : null; + public byte[] prefix(final int id) { + return prefixes.key(id); } /** - * Returns the specified namespace URI. - * @param id namespace URI reference - * @return prefix + * Returns a namespace uri for the specified id. + * @param id id of namespace uri + * @return namespace uri */ public byte[] uri(final int id) { return uris.key(id); } /** - * Returns the namespace URI reference for the specified name and pre value, - * or {@code 0} if namespace cannot be found. - * @param name element/attribute name - * @param pre pre value - * @param data data reference - * @return namespace URI reference + * Returns the id of the specified namespace uri. + * @param uri namespace URI + * @return id, or {@code 0} if no entry is found */ - public int uri(final byte[] name, final int pre, final Data data) { - return uri(Token.prefix(name), current.find(pre, data)); + public int uriId(final byte[] uri) { + return uris.id(uri); } /** - * Returns a reference to the specified namespace URI, - * or {@code 0} if the URI is empty or no namespace is found. - * @param uri namespace URI - * @return reference, or {@code 0} + * Returns the id of the specified prefix. + * @param prefix prefix + * @return id, or {@code 0} if no entry is found */ - public int uri(final byte[] uri) { - return uri.length == 0 ? 0 : uris.id(uri); + public int prefixId(final byte[] prefix) { + return prefixes.id(prefix); } /** - * Returns the namespace URI reference for the specified name, - * or {@code 0} if no namespace is found. - * @param name element/attribute name - * @param elem element flag - * @return namespace + * Returns the default namespace uri for all documents of the database. + * @return global default namespace, or {@code null} if there is more than one such namespace */ - public int uri(final byte[] name, final boolean elem) { - if(uris.isEmpty()) return 0; - final byte[] pref = Token.prefix(name); - int nu = elem ? defaults.get(level) : 0; - if(pref.length != 0) nu = uri(pref, current); - return nu; + public byte[] globalUri() { + // no namespaces defined: default namespace is empty + final int ch = root.children(); + if(ch == 0) return Token.EMPTY; + // give up if more than one namespace is defined + if(ch > 1) return null; + // give up if child node has more children or more than one namespace + final NSNode child = root.child(0); + final int[] values = child.values(); + if(child.children() > 0 || child.pre() != 1 || values.length != 2) return null; + // give up if namespace has a non-empty prefix + if(prefix(values[0]).length != 0) return null; + // return default namespace + return uri(values[1]); } + // Requesting Namespaces Based on Context ======================================================= + /** - * Returns the current namespaces node. - * @return current namespace node + * Returns the id of a namespace uri for the specified prefix. + * @param prefix prefix + * @param element indicates if the prefix belongs to an element or attribute name + * @return id of namespace uri, or {@code 0} if no entry is found */ - NSNode getCurrent() { - return current; + public int uriIdForPrefix(final byte[] prefix, final boolean element) { + if(isEmpty()) return 0; + return prefix.length != 0 ? uriId(prefix, cursor) : element ? defaults.get(level) : 0; } /** - * Returns the specified prefix. - * @param id prefix reference - * @return prefix + * Returns the id of a namespace uri for the specified prefix and pre value. + * @param prefix prefix + * @param pre pre value + * @param data data reference + * @return id of namespace uri, or {@code 0} if no entry is found */ - byte[] prefix(final int id) { - return prefs.key(id); + public int uriIdForPrefix(final byte[] prefix, final int pre, final Data data) { + return uriId(prefix, cursor.find(pre, data)); } /** - * Returns the prefix and URI references of all namespaces defined for the node with - * the specified pre value. - * @param pre pre value - * @param data data reference - * @return namespace references + * Returns the id of a namespace uri for the specified prefix and node. + * @param prefix prefix + * @param node node to start with + * @return id of the namespace uri, or {@code 0} if namespace is not found. */ - int[] get(final int pre, final Data data) { - return current.find(pre, data).values; + private int uriId(final byte[] prefix, final NSNode node) { + final int prefId = prefixes.id(prefix); + if(prefId == 0) return 0; + + NSNode nd = node; + while(nd != null) { + final int uriId = nd.uri(prefId); + if(uriId != 0) return uriId; + nd = nd.parent(); + } + return 0; } /** - * Returns a map with all namespaces that are valid for the specified pre value. + * Returns all namespace prefixes and uris that are declared for the specified pre value. + * Should only be called for element nodes. * @param pre pre value * @param data data reference - * @return scope + * @return key and value ids */ - TokenMap scope(final int pre, final Data data) { - final TokenMap nsScope = new TokenMap(); - NSNode node = current; - do { - final int[] values = node.values; - final int vl = values.length; - for(int v = 0; v < vl; v += 2) { - nsScope.put(prefix(values[v]), uri(values[v + 1])); - } - final int pos = node.find(pre); - if(pos < 0) break; - node = node.children[pos]; - } while(node.pr <= pre && pre < node.pr + data.size(node.pr, Data.ELEM)); - return nsScope; + public Atts values(final int pre, final Data data) { + final Atts as = new Atts(); + final int[] values = cursor.find(pre, data).values(); + final int nl = values.length; + for(int n = 0; n < nl; n += 2) as.add(prefix(values[n]), uri(values[n + 1])); + return as; } /** - * Finds the nearest namespace node on the ancestor axis of the insert - * location and sets it as new root. Possible candidates for this node are collected - * and the match with the highest pre value between ancestors and candidates - * is determined. + * Finds the nearest namespace node on the ancestor axis of the insert location and sets it as new + * root. Possible candidates for this node are collected and the match with the highest pre value + * between ancestors and candidates is determined. * @param pre pre value * @param data data reference */ void root(final int pre, final Data data) { // collect possible candidates for namespace root final List cand = new LinkedList<>(); - NSNode node = root; - cand.add(node); - for(int p; (p = node.find(pre)) > -1;) { + NSNode nd = root; + cand.add(nd); + for(int p; (p = nd.find(pre)) > -1;) { // add candidate to stack - node = node.children[p]; - cand.add(0, node); + nd = nd.child(p); + cand.add(0, nd); } - node = root; + nd = root; if(cand.size() > 1) { // compare candidates to ancestors of pre value int ancPre = pre; // take first candidate from stack NSNode curr = cand.remove(0); - while(ancPre > -1 && node == root) { - // this is the new root - if(curr.pr == ancPre) node = curr; - // if the current candidate's pre value is lower than the current - // ancestor of par or par itself, we have to look for a potential - // match for this candidate. therefore we iterate through ancestors - // until we find one with a lower than or the same pre value as the + while(ancPre > -1 && nd == root) { + // if the current candidate's pre value is lower than the current ancestor of par or par + // itself, we have to look for a potential match for this candidate. therefore we iterate + // through ancestors until we find one with a lower than or the same pre value as the // current candidate. - else if(curr.pr < ancPre) { - while((ancPre = data.parent(ancPre, data.kind(ancPre))) > curr.pr); - if(curr.pr == ancPre) node = curr; - } + while(ancPre > curr.pre()) ancPre = data.parent(ancPre, data.kind(ancPre)); + // this is the new root + if(ancPre == curr.pre()) nd = curr; // no potential for infinite loop, because dummy root is always a match, // in this case ancPre ends iteration if(!cand.isEmpty()) curr = cand.remove(0); } } - final int uri = uri(Token.EMPTY, pre, data); - defaults.set(level, uri); - // remind uri before insert of first node n to connect siblings of n to - // according namespace - defaults.set(level - 1, uri); - current = node; + final int uriId = uriIdForPrefix(Token.EMPTY, pre, data); + defaults.set(level, uriId); + // remember uri before insert of first node n to connect siblings of n to according namespace + defaults.set(level - 1, uriId); + cursor = nd; } /** - * Returns the namespace URI reference for the specified prefix and node, - * or {@code 0} if no namespace is found. - * @param pref prefix - * @param node node to start with - * @return namespace URI reference - */ - private int uri(final byte[] pref, final NSNode node) { - final int id = prefs.id(pref); - if(id == 0) return 0; - - NSNode n = node; - while(n != null) { - final int u = n.uri(id); - if(u != 0) return u; - n = n.parent; - } - return 0; - } - - /** - * Returns all namespace nodes in the namespace structure with a minimum - * PRE value. - * @param pre minimum PRE value of a namespace node. - * @return List of namespace nodes with a minimum PRE value of pre + * Caches and returns all namespace nodes in the namespace structure with a minimum pre value. + * @param pre minimum pre value of a namespace node. + * @return list of namespace nodes */ - List getNSNodes(final int pre) { - final List l = new ArrayList<>(); - addNSNodes(root, l, pre); - return l; + ArrayList cache(final int pre) { + final ArrayList list = new ArrayList<>(); + addNodes(root, list, pre); + return list; } /** - * Recursively adds namespace nodes to the given list, starting at the children of the given node. - * @param curr current namespace node + * Recursively adds namespace nodes to the a list, starting with the children of a node. + * @param node current namespace node * @param list list with namespace nodes * @param pre pre value */ - private static void addNSNodes(final NSNode curr, final List list, final int pre) { - final int sz = curr.sz; - int s = find(curr, pre); - while(s > 0 && (s == sz || curr.children[s].pr >= pre)) s--; - for(; s < sz; s++) { - final NSNode ch = curr.children[s]; - if(ch.pr >= pre) list.add(ch); - addNSNodes(ch, list, pre); + private static void addNodes(final NSNode node, final List list, final int pre) { + final int sz = node.children(); + int i = Math.max(0, node.find(pre)); + while(i > 0 && (i == sz || node.child(i).pre() >= pre)) i--; + for(; i < sz; i++) { + final NSNode child = node.child(i); + if(child.pre() >= pre) list.add(child); + addNodes(child, list, pre); } } - /** - * Returns the index of the specified pre value. - * @param curr current namespace node - * @param pre int pre value - * @return index - */ - private static int find(final NSNode curr, final int pre) { - // binary search - int l = 0, h = curr.sz - 1; - while(l <= h) { - final int m = l + h >>> 1; - final int c = curr.children[m].pr - pre; - if(c == 0) return m; - if(c < 0) l = m + 1; - else h = m - 1; - } - return l; - } + // Updating Namespaces ========================================================================== - // Updating Namespaces ================================================================ /** - * Deletes the specified namespace URI from the root node. - * @param uri namespace URI reference + * Sets a namespace cursor. + * @param node namespace node */ - public void delete(final byte[] uri) { - final int id = uris.id(uri); - if(id != 0) current.delete(id); + void cursor(final NSNode node) { + cursor = node; } /** - * Deletes the specified number of entries from the namespace structure. - * @param pre pre value of the first node to delete - * @param data data reference - * @param size number of entries to be deleted + * Returns the current namespace cursor. + * @return current namespace node */ - void delete(final int pre, final int size, final Data data) { - NSNode nd = current.find(pre, data); - if(nd.pr == pre) nd = nd.parent; - while(nd != null) { - nd.delete(pre, size); - nd = nd.parent; - } - - decrementPre(root, pre, size); + NSNode cursor() { + return cursor; } /** - * Recursive shifting of pre values after delete operations. - * @param node current namespace node which is updated if necessary - * @param pre update location - * @param size size of inserted/deleted node + * Increases the level counter and sets a new default namespace. */ - private static void decrementPre(final NSNode node, final int pre, final int size) { - if(node.pr >= pre + size) node.pr -= size; - final int ns = node.sz; - for(int n = 0; n < ns; n++) decrementPre(node.children[n], pre, size); + public void open() { + final int nu = defaults.get(level); + defaults.set(++level, nu); } /** - * Increments the PRE value of all namespace nodes in the given list by the given size. - * @param list list of namespace nodes - * @param inc size to increment + * Adds namespaces to a new namespace child node and sets this node as new cursor. + * @param pre pre value + * @param atts namespaces */ - static void incrementPre(final List list, final int inc) { - for(final NSNode node : list) node.pr += inc; + public void open(final int pre, final Atts atts) { + open(); + if(!atts.isEmpty()) { + final NSNode nd = new NSNode(pre); + cursor.add(nd); + cursor = nd; + + final int as = atts.size(); + for(int a = 0; a < as; a++) { + final byte[] pref = atts.name(a), uri = atts.value(a); + final int prefId = prefixes.put(pref), uriId = uris.put(uri); + nd.add(prefId, uriId); + if(pref.length == 0) defaults.set(level, uriId); + } + } } /** - * Adds a namespace for the specified pre value. + * Adds a single namespace for the specified pre value. * @param pre pre value - * @param par parent value - * @param pref prefix - * @param uri uri + * @param prefix prefix + * @param uri namespace uri * @param data data reference - * @return uri reference + * @return id of namespace uri */ - public int add(final int pre, final int par, final byte[] pref, final byte[] uri, - final Data data) { - - // don't store XML namespace - if(Token.eq(pref, Token.XML)) return 0; + public int add(final int pre, final byte[] prefix, final byte[] uri, final Data data) { + final int prefId = prefixes.put(prefix), uriId = uris.put(uri); + NSNode nd = cursor.find(pre, data); + if(nd.pre() != pre) { + final NSNode child = new NSNode(pre); + nd.add(child); + nd = child; + } + nd.add(prefId, uriId); + return uriId; + } - final NSNode nd = current.find(par, data); - final NSNode t = new NSNode(pre); - - final int k = prefs.put(pref); - final int v = uris.put(uri); - if(nd.pr == pre) { - nd.add(k, v); - } else { - t.add(k, v); - nd.add(t); + /** + * Closes a namespace node. + * @param pre current pre value + */ + public void close(final int pre) { + while(cursor.pre() >= pre) { + final NSNode nd = cursor.parent(); + if(nd == null) break; + cursor = nd; } - return v; + --level; + } + + /** + * Deletes the specified namespace URI from the root node. + * @param uri namespace URI reference + */ + public void delete(final byte[] uri) { + final int id = uris.id(uri); + if(id != 0) cursor.delete(id); } /** - * Setter for namespaces root node. - * @param node new root + * Deletes the specified number of entries from the namespace structure. + * @param pre pre value of the first node to delete + * @param data data reference + * @param size number of entries to be deleted */ - void setCurrent(final NSNode node) { - current = node; + void delete(final int pre, final int size, final Data data) { + NSNode nd = cursor.find(pre, data); + if(nd.pre() == pre) nd = nd.parent(); + while(nd != null) { + nd.delete(pre, size); + nd = nd.parent(); + } + root.decrementPre(pre, size); } - // Printing Namespaces ================================================================ + // Printing Namespaces ========================================================================== /** - * Returns a tabular representation of the namespaces. - * @param start start pre value - * @param end end pre value + * Returns a tabular representation of the namespace entries. + * @param start first pre value + * @param end last pre value * @return namespaces */ public byte[] table(final int start, final int end) { - if(root.sz == 0) return Token.EMPTY; + if(root.children() == 0) return Token.EMPTY; final Table t = new Table(); - t.header.add(TABLEID); + t.header.add(TABLENS); t.header.add(TABLEPRE); t.header.add(TABLEDIST); t.header.add(TABLEPREF); t.header.add(TABLEURI); for(int i = 0; i < 3; ++i) t.align.add(true); - table(t, root, start, end); + root.table(t, start, end, this); return t.contents.isEmpty() ? Token.EMPTY : t.finish(); } /** - * Adds the namespace structure for a node to the specified table. - * @param table table - * @param node namespace node - * @param start start pre value - * @param end end pre value - */ - private void table(final Table table, final NSNode node, final int start, final int end) { - final int[] values = node.values; - final int vl = values.length; - for(int i = 0; i < vl; i += 2) { - if(node.pr < start || node.pr > end) continue; - final TokenList tl = new TokenList(); - tl.add(values[i + 1]); - tl.add(node.pr); - tl.add(node.pr - node.parent.pr); - tl.add(prefs.key(values[i])); - tl.add(uris.key(values[i + 1])); - table.contents.add(tl); - } - for(int i = 0; i < node.sz; i++) table(table, node.children[i], start, end); - } - - /** * Returns namespace information. * @return info string */ public byte[] info() { final TokenObjMap map = new TokenObjMap<>(); - info(map, root); + root.info(map, this); final TokenBuilder tb = new TokenBuilder(); for(final byte[] key : map) { tb.add(" "); @@ -515,34 +431,13 @@ } /** - * Adds namespace information for the specified node to a map. - * @param map namespace map - * @param node namespace node - */ - private void info(final TokenObjMap map, final NSNode node) { - final int[] values = node.values; - final int vl = values.length; - for(int v = 0; v < vl; v += 2) { - final byte[] key = uris.key(values[v + 1]); - final byte[] val = prefs.key(values[v]); - TokenList old = map.get(key); - if(old == null) { - old = new TokenList(); - map.put(key, old); - } - if(!old.contains(val)) old.add(val); - } - for(final NSNode c : node.children) info(map, c); - } - - /** * Returns a string representation of the namespaces. * @param start start pre value * @param end end pre value * @return string */ public String toString(final int start, final int end) { - return root.print(this, start, end); + return root.toString(this, start, end); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/NSNode.java basex-8.2.3/basex-core/src/main/java/org/basex/data/NSNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/NSNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/NSNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,6 +8,8 @@ import org.basex.io.in.DataInput; import org.basex.io.out.DataOutput; import org.basex.util.*; +import org.basex.util.hash.*; +import org.basex.util.list.*; /** * This class stores a single namespace node. @@ -16,25 +18,25 @@ * @author Christian Gruen */ final class NSNode { - /** Children. */ - NSNode[] children; + /** Child nodes. */ + private NSNode[] nodes; /** Number of children. */ - int sz; + private int size; /** Parent node. */ - NSNode parent; - /** References to Prefix/URI pairs. */ - int[] values; + private NSNode parent; + /** Dense array with ids of prefix/namespace uri pairs. */ + private int[] values; /** Pre value. */ - int pr; + private int pre; /** * Default constructor. * @param pre pre value */ NSNode(final int pre) { + this.pre = pre; values = new int[0]; - children = new NSNode[0]; - pr = pre; + nodes = new NSNode[0]; } /** @@ -45,11 +47,11 @@ */ NSNode(final DataInput in, final NSNode parent) throws IOException { this.parent = parent; - pr = in.readNum(); + pre = in.readNum(); values = in.readNums(); - sz = in.readNum(); - children = new NSNode[sz]; - for(int c = 0; c < sz; ++c) children[c] = new NSNode(in, this); + size = in.readNum(); + nodes = new NSNode[size]; + for(int n = 0; n < size; ++n) nodes[n] = new NSNode(in, this); } /** @@ -58,106 +60,117 @@ * @throws IOException I/O exception */ void write(final DataOutput out) throws IOException { - out.writeNum(pr); + out.writeNum(pre); out.writeNums(values); - out.writeNum(sz); - for(int c = 0; c < sz; ++c) children[c].write(out); + out.writeNum(size); + for(int c = 0; c < size; ++c) nodes[c].write(out); } /** - * Adds the specified node into the child array, which is sorted by pre values. - * @param node child node + * Returns the specified child. + * @param i index + * @return child */ - void add(final NSNode node) { - if(sz == children.length) - children = Array.copy(children, new NSNode[Array.newSize(sz)]); - - // find inserting position - int s = find(node.pr); - if(s < 0 || node.pr != children[s].pr) s++; + NSNode child(final int i) { + return nodes[i]; + } - System.arraycopy(children, s, children, s + 1, sz++ - s); - children[s] = node; - node.parent = this; + /** + * Returns the number of children. + * @return number of children + */ + int children() { + return size; } /** - * Adds the specified prefix and URI reference. - * @param prefix prefix reference - * @param uri uri reference + * Returns the pre value. + * @return pre value */ - void add(final int prefix, final int uri) { - final int s = values.length; - values = Arrays.copyOf(values, s + 2); - values[s] = prefix; - values[s + 1] = uri; + int pre() { + return pre; } /** - * Recursively deletes the specified namespace URI reference. - * @param uri namespace URI reference + * Returns the parent node. + * @return parent node */ - void delete(final int uri) { - for(int c = 0; c < sz; ++c) children[c].delete(uri); + NSNode parent() { + return parent; + } - final int vl = values.length; - for(int v = 0; v < vl; v += 2) { - if(values[v + 1] != uri) continue; - final int[] vals = new int[vl - 2]; - System.arraycopy(values, 0, vals, 0, v); - System.arraycopy(values, v + 2, vals, v, vl - v - 2); - values = vals; - break; - } + /** + * Returns the ids of prefix/namespace uri pairs. + * @return prefix/namespace uri pairs + */ + int[] values() { + return values; } - // Requesting Namespaces ==================================================== + // Requesting Namespaces ======================================================================== /** - * Finds the closest namespace node for the specified pre value. - * @param pre pre value + * Finds the namespace node that is located closest to the specified pre value. + * @param p pre value * @param data data reference * @return node */ - NSNode find(final int pre, final Data data) { - final int s = find(pre); - // no match found: return current node + NSNode find(final int p, final Data data) { + // return this node if the pre values of all children are greater than the searched value + final int s = find(p); if(s == -1) return this; - final NSNode ch = children[s]; - final int cp = ch.pr; + + final NSNode ch = nodes[s]; + final int cp = ch.pre; // return exact hit - if(cp == pre) return ch; + if(cp == p) return ch; // found node is preceding sibling - if(cp + data.size(cp, Data.ELEM) <= pre) return this; + if(cp + data.size(cp, Data.ELEM) <= p) return this; // continue recursive search - return children[s].find(pre, data); + return nodes[s].find(p, data); } /** - * Finds a specific pre value in the child array utilizing binary search - * and returns its position if it is contained. - * If it is not contained, it returns the position of the biggest element in - * the array that is still smaller than p. If all elements in the array are - * bigger, it returns -1. - * @param pre pre value - * @return position of node in child array. + * Locates a child node with the specified pre value. + *
      + *
    • If the value is found, the position of the child node is returned.
    • + *
    • Otherwise, the position of the last child with a smaller pre value is returned.
    • + *
    • -1 is returned if all children have greater pre values.
    • + *
    + * @param p pre value + * @return position of the child node */ - int find(final int pre) { - int l = 0, h = sz - 1; + int find(final int p) { + int l = 0, h = size - 1; while(l <= h) { // binary search - final int m = l + h >>> 1; - final int v = children[m].pr; - if(v == pre) return m; - if(v < pre) l = m + 1; + final int m = l + h >>> 1, v = nodes[m].pre; + if(v == p) return m; + if(v < p) l = m + 1; else h = m - 1; } return l - 1; } /** - * Returns the namespace URI reference for the specified prefix. + * Returns the index of the specified pre value. + * @param p int pre value + * @return index, or possible insertion position + */ + int find2(final int p) { + int l = 0, h = size - 1; + while(l <= h) { // binary search + final int m = l + h >>> 1, v = nodes[m].pre; + if(v == p) return m; + if(v < p) l = m + 1; + else h = m - 1; + } + return l; + } + + /** + * Returns the id of the namespace uri for the specified prefix. * @param prefix prefix reference - * @return uri reference or {@code 0} + * @return if of the namespace uri, or {@code 0} if none is found */ int uri(final int prefix) { final int[] vls = values; @@ -166,52 +179,102 @@ return 0; } + // Updating Namespaces ========================================================================== + /** - * Deletes nodes in the specified range (p .. p + sz - 1) and updates the - * following pre values - * @param pre pre value - * @param size number of nodes to be deleted, or actually the size of the pre + * Deletes nodes in the specified range (p .. p + sz - 1) and updates the following pre values. + * @param p pre value + * @param s number of nodes to be deleted, or actually the size of the pre * value which is to be deleted */ - void delete(final int pre, final int size) { - // find the pre value which must be deleted - int s = find(pre); - /* if the node is not directly contained as a child, either start at array - * index 0 or proceed with the next node in the child array to search for - * descendants of pre - */ - if(s == -1 || children[s].pr != pre) ++s; + void delete(final int p, final int s) { + // find the node to deleted + int d = find(p); + // if the node is not directly contained as a child, either start at array index 0 or + // proceed with the next node in the child array to search for descendants of pre + if(d == -1 || nodes[d].pre != p) ++d; // first pre value which is not deleted - final int upper = pre + size; + final int upper = p + s; // number of nodes to be deleted int num = 0; // determine number of nodes to be deleted - for(int i = s; i < sz && children[i].pr < upper; ++i, ++num); + for(int i = d; i < size && nodes[i].pre < upper; ++i, ++num); // new size of child array - sz -= num; + size -= num; // if all nodes are deleted, just create an empty array - if(sz == 0) children = new NSNode[0]; - + if(size == 0) nodes = new NSNode[0]; // otherwise remove nodes from the child array - else if(num > 0) System.arraycopy(children, s + num, children, s, sz - s); + else if(num > 0) System.arraycopy(nodes, d + num, nodes, d, size - d); } - // Printing Namespaces ====================================================== + /** + * Adds the specified node into the child array, which is sorted by pre values. + * @param node child node + */ + void add(final NSNode node) { + if(size == nodes.length) + nodes = Array.copy(nodes, new NSNode[Array.newSize(size)]); + + // find inserting position + int s = find(node.pre); + if(s < 0 || node.pre != nodes[s].pre) s++; + + System.arraycopy(nodes, s, nodes, s + 1, size++ - s); + nodes[s] = node; + node.parent = this; + } /** - * Prints the node structure for debugging purposes. - * @param ns namespace reference - * @param start start pre value - * @param end end pre value - * @return string + * Adds the specified prefix and URI reference. + * @param prefix prefix reference + * @param uri uri reference */ - String print(final Namespaces ns, final int start, final int end) { - final TokenBuilder tb = new TokenBuilder(); - print(tb, 0, ns, start, end); - return tb.toString(); + void add(final int prefix, final int uri) { + final int s = values.length; + values = Arrays.copyOf(values, s + 2); + values[s] = prefix; + values[s + 1] = uri; + } + + /** + * Recursively deletes the specified namespace URI reference. + * @param uri namespace URI reference + */ + void delete(final int uri) { + for(int c = 0; c < size; ++c) nodes[c].delete(uri); + + final int vl = values.length; + for(int v = 0; v < vl; v += 2) { + if(values[v + 1] != uri) continue; + final int[] vals = new int[vl - 2]; + System.arraycopy(values, 0, vals, 0, v); + System.arraycopy(values, v + 2, vals, v, vl - v - 2); + values = vals; + break; + } + } + + /** + * Recursive shifting of pre values after delete operations. + * @param start update location + * @param diff value to subtract from pre value + */ + void decrementPre(final int start, final int diff) { + if(pre >= start + diff) pre -= diff; + for(int c = 0; c < size; c++) nodes[c].decrementPre(start, diff); + } + + /** + * Increments the pre value by the specified size. + * @param diff value to add to pre value + */ + void incrementPre(final int diff) { + pre += diff; } + // Printing Namespaces ========================================================================== + /** * Prints the node structure for debugging purposes. * @param tb token builder @@ -223,24 +286,79 @@ private void print(final TokenBuilder tb, final int level, final Namespaces ns, final int start, final int end) { - if(pr >= start && pr <= end) { + if(pre >= start && pre <= end) { tb.add(NL); for(int i = 0; i < level; ++i) tb.add(" "); tb.add(toString() + ' '); final int[] vls = values; final int vl = vls.length; for(int i = 0; i < vl; i += 2) { + if(i != 0) tb.add(' '); tb.add("xmlns"); final byte[] p = ns.prefix(vls[i]); if(p.length != 0) tb.add(':'); - tb.add(p).add("=\"").add(ns.uri(vls[i + 1])).add("\" "); + tb.add(p).add("=\"").add(ns.uri(vls[i + 1])).add('"'); } } - for(int c = 0; c < sz; ++c) children[c].print(tb, level + 1, ns, start, end); + for(int c = 0; c < size; ++c) nodes[c].print(tb, level + 1, ns, start, end); + } + + /** + * Adds the namespace structure of a node to the specified table. + * @param table table + * @param start first pre value + * @param end last pre value + * @param ns namespace reference + */ + void table(final Table table, final int start, final int end, final Namespaces ns) { + final int vl = values.length; + for(int i = 0; i < vl; i += 2) { + if(pre < start || pre > end) continue; + final TokenList tl = new TokenList(); + tl.add(values[i + 1]); + tl.add(pre); + tl.add(pre - parent.pre); + tl.add(ns.prefix(values[i])); + tl.add(ns.uri(values[i + 1])); + table.contents.add(tl); + } + for(int i = 0; i < size; i++) nodes[i].table(table, start, end, ns); + } + + /** + * Adds namespace information for the specified node to a map. + * @param map namespace map + * @param ns namespace reference + */ + void info(final TokenObjMap map, final Namespaces ns) { + final int vl = values.length; + for(int v = 0; v < vl; v += 2) { + final byte[] pref = ns.prefix(values[v]), uri = ns.uri(values[v + 1]); + TokenList prfs = map.get(uri); + if(prfs == null) { + prfs = new TokenList(1); + map.put(uri, prfs); + } + if(!prfs.contains(pref)) prfs.add(pref); + } + for(int c = 0; c < size; ++c) nodes[c].info(map, ns); + } + + /** + * Prints the node structure. + * @param ns namespace reference + * @param start start pre value + * @param end end pre value + * @return string + */ + String toString(final Namespaces ns, final int start, final int end) { + final TokenBuilder tb = new TokenBuilder(); + print(tb, 0, ns, start, end); + return tb.toString(); } @Override public String toString() { - return "Pre[" + pr + ']'; + return "Pre[" + pre + ']'; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/NSScope.java basex-8.2.3/basex-core/src/main/java/org/basex/data/NSScope.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/NSScope.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/NSScope.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,92 @@ +package org.basex.data; + +import java.util.*; + +import org.basex.util.*; +import org.basex.util.list.*; + +/** + * This class organizes namespace scopes. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +final class NSScope { + /** Data reference. */ + private final Data data; + /** Namespaces. */ + private final Namespaces nspaces; + /** Stack with pre values. */ + private final IntList preStack = new IntList(); + /** Root namespace. */ + private final NSNode root; + /** Tracks existing nodes. */ + private final ArrayList cache; + + /** + * Default constructor. + * @param pre pre value + * @param data data reference + */ + NSScope(final int pre, final Data data) { + this.data = data; + nspaces = data.nspaces; + root = nspaces.cursor(); + cache = nspaces.cache(pre); + } + + /** + * Refreshes the namespace structure. + * @param nsPre pre value with namespaces + * @param c insertion counter + */ + void loop(final int nsPre, final int c) { + if(c == 0) nspaces.root(nsPre, data); + while(!preStack.isEmpty() && preStack.peek() > nsPre) nspaces.close(preStack.pop()); + } + + /** + * Opens a new level. + * @param pre pre value + */ + void open(final int pre) { + nspaces.open(); + preStack.push(pre); + } + + /** + * Parses the specified namespaces and returns all namespaces that are not declared yet. + * @param pre pre value + * @param nsp source namespaces + * @return {@code true} if new namespaces were added + */ + boolean open(final int pre, final Atts nsp) { + // collect new namespaces + final Atts ns = new Atts(); + final int as = nsp.size(); + for(int a = 0; a < as; a++) { + final byte[] prefix = nsp.name(a), uri = nsp.value(a); + final int uriId = nspaces.uriIdForPrefix(prefix, true); + if(uriId == 0 || uriId != nspaces.uriId(uri)) ns.add(prefix, uri); + } + nspaces.open(pre, ns); + preStack.push(pre); + return !ns.isEmpty(); + } + + /** + * Shifts cached namespaces by the specified value. + * @param diff shift + */ + void shift(final int diff) { + for(final NSNode node : cache) node.incrementPre(diff); + } + + /** + * Closes the namespace scope. + */ + void close() { + while(!preStack.isEmpty()) nspaces.close(preStack.pop()); + nspaces.cursor(root); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/data/Result.java basex-8.2.3/basex-core/src/main/java/org/basex/data/Result.java --- basex-8.1.1/basex-core/src/main/java/org/basex/data/Result.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/data/Result.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -package org.basex.data; - -import java.io.*; - -import org.basex.io.serial.*; - -/** - * This is an interface for query results. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -public interface Result { - /** - * Number of items, stored in the result instance. - * @return number of items - */ - long size(); - - /** - * Serializes the result, using the standard serializer. - * @return serialized value - * @throws IOException I/O exception - */ - String serialize() throws IOException; - - /** - * Serializes the result. - * @param ser serializer - * @throws IOException I/O exception - */ - void serialize(Serializer ser) throws IOException; - - /** - * Serializes the item with the specified index. - * @param ser serializer - * @param index offset of result to serialize - * @throws IOException I/O exception - */ - void serialize(Serializer ser, int index) throws IOException; -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/dialog/DialogColors.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/dialog/DialogColors.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/dialog/DialogColors.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/dialog/DialogColors.java 2015-07-14 10:54:40.000000000 +0000 @@ -79,9 +79,9 @@ public void action(final Object comp) { if(comp instanceof BaseXButton) { // reset default values - sliderRed.setValue(GUIOptions.COLORRED.value); - sliderGreen.setValue(GUIOptions.COLORGREEN.value); - sliderBlue.setValue(GUIOptions.COLORBLUE.value); + sliderRed.setValue(GUIOptions.COLORRED.value()); + sliderGreen.setValue(GUIOptions.COLORGREEN.value()); + sliderBlue.setValue(GUIOptions.COLORBLUE.value()); } sliderRed.assign(); sliderGreen.assign(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/dialog/DialogGeneralPrefs.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/dialog/DialogGeneralPrefs.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/dialog/DialogGeneralPrefs.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/dialog/DialogGeneralPrefs.java 2015-07-14 10:54:40.000000000 +0000 @@ -134,7 +134,7 @@ * @return maximum number of hits */ private int hitsForSlider() { - int mh = gui.gopts.get(MainOptions.MAXHITS); + int mh = gui.gopts.get(GUIOptions.MAXRESULTS); if(mh == -1) mh = Integer.MAX_VALUE; final int hl = HITS.length - 1; int h = -1; @@ -178,7 +178,7 @@ } opts.set(StaticOptions.LANG, lang.getSelectedItem()); - gui.gopts.set(GUIOptions.MAXHITS, HITS[limit.getValue()]); + gui.gopts.set(GUIOptions.MAXRESULTS, HITS[limit.getValue()]); creds.setText(TRANSLATION + COLS + creds(lang.getSelectedItem())); final int mh = HITS[limit.getValue()]; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/dialog/DialogMem.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/dialog/DialogMem.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/dialog/DialogMem.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/dialog/DialogMem.java 2015-07-14 10:54:40.000000000 +0000 @@ -69,8 +69,7 @@ if(vis) return; // regularly refresh panel - final Timer timer = new Timer(true); - timer.scheduleAtFixedRate(new TimerTask() { + new Timer(true).scheduleAtFixedRate(new TimerTask() { @Override public void run() { if(isVisible() && !text.selected()) text.setText(info()); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/dialog/DialogProps.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/dialog/DialogProps.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/dialog/DialogProps.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/dialog/DialogProps.java 2015-07-14 10:54:40.000000000 +0000 @@ -125,7 +125,7 @@ final String db = InfoDB.db(data.meta, true, false, true); final TokenBuilder info = new TokenBuilder(db); - if(data.nspaces.size() != 0) { + if(!data.nspaces.isEmpty()) { info.bold().add(NL + NAMESPACES + NL).norm().add(data.nspaces.info()); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/GUI.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/GUI.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/GUI.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/GUI.java 2015-07-14 10:54:40.000000000 +0000 @@ -29,6 +29,8 @@ import org.basex.io.*; import org.basex.io.out.*; import org.basex.query.*; +import org.basex.query.value.*; +import org.basex.query.value.seq.*; import org.basex.util.*; import org.basex.util.options.*; @@ -377,10 +379,10 @@ // check and add default namespace final Data data = context.data(); final Namespaces ns = data.nspaces; - String in = qu.trim().isEmpty() ? "()" : qu; - final int u = ns.uri(Token.EMPTY, 0, data); - if(u != 0) in = Util.info("declare default element namespace \"%\"; %", ns.uri(u), in); - execute(edit, new XQuery(in)); + String q = qu.trim().isEmpty() ? "()" : qu; + final int uriId = ns.uriIdForPrefix(Token.EMPTY, 0, data); + if(uriId != 0) q = Util.info("declare default element namespace \"%\"; %", ns.uri(uriId), q); + execute(edit, new XQuery(q)); } /** @@ -449,6 +451,10 @@ // execute command and cache result final ArrayOutput ao = new ArrayOutput(); ao.setLimit(gopts.get(GUIOptions.MAXTEXT)); + // sets the maximum number of hits + cmd.maxResults(gopts.get(GUIOptions.MAXRESULTS)); + + // checks if the command is updating updating = cmd.updating(context); // updates the query editor @@ -491,7 +497,7 @@ } } else { // get query result - final Result result = cmd.finish(); + final Value result = cmd.finish(); DBNodes nodes = result instanceof DBNodes && result.size() != 0 ? (DBNodes) result : null; final boolean updated = cmd.updated(context); @@ -507,7 +513,7 @@ // check if result has changed final boolean flt = gopts.get(GUIOptions.FILTERRT); final DBNodes curr = context.current(); - if(flt || curr != null && !curr.equals(current)) { + if(flt || curr != null && !curr.sameAs(current)) { // refresh context if at least one node was found if(nodes != null) notify.context(nodes, flt, null); } else if(context.marked != null) { @@ -674,7 +680,7 @@ * @param n number of results */ private void setResults(final long n) { - int mh = context.options.get(MainOptions.MAXHITS); + int mh = gopts.get(GUIOptions.MAXRESULTS); if(mh < 0) mh = Integer.MAX_VALUE; hits.setText(Util.info(RESULTS_X, (n >= mh ? "\u2265" : "") + n)); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/GUIMenuCmd.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/GUIMenuCmd.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/GUIMenuCmd.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/GUIMenuCmd.java 2015-07-14 10:54:40.000000000 +0000 @@ -15,6 +15,7 @@ import org.basex.query.func.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; +import org.basex.query.value.seq.*; import org.basex.query.value.type.*; import org.basex.util.*; import org.basex.util.list.*; @@ -240,7 +241,7 @@ C_COPYPATH(COPY_PATH, "% shift C", true, false) { @Override public void execute(final GUI gui) { - final int pre = gui.context.marked.pres[0]; + final int pre = gui.context.marked.pre(0); BaseXLayout.copy(Token.string(ViewData.path(gui.context.data(), pre))); } @@ -258,7 +259,7 @@ public void execute(final GUI gui) { final Context ctx = gui.context; final DBNodes n = ctx.marked; - ctx.copied = new DBNodes(n.data, n.pres); + ctx.copied = new DBNodes(n.data(), n.pres()); } @Override @@ -304,7 +305,7 @@ if(i > 0) sb.append(','); sb.append(openPre(n, i)); } - gui.context.marked = new DBNodes(n.data); + gui.context.marked = new DBNodes(n.data()); gui.context.copied = null; gui.context.focused = -1; gui.execute(new XQuery("delete nodes (" + sb + ')')); @@ -350,7 +351,7 @@ @Override public void execute(final GUI gui) { final DBNodes n = gui.context.marked; - final DialogEdit edit = new DialogEdit(gui, n.pres[0]); + final DialogEdit edit = new DialogEdit(gui, n.pre(0)); if(!edit.ok()) return; String rename = null; @@ -784,7 +785,7 @@ // check if all nodes are document nodes boolean doc = true; final Data data = ctx.data(); - for(final int pre : ctx.current().pres) doc &= data.kind(pre) == Data.DOC; + for(final int pre : ctx.current().pres()) doc &= data.kind(pre) == Data.DOC; final DBNodes nodes; if(doc) { // if yes, jump to database root @@ -793,7 +794,7 @@ } else { // otherwise, jump to parent nodes final IntList pres = new IntList(); - for(final int pre : ctx.current().pres) { + for(final int pre : ctx.current().pres()) { final int k = data.kind(pre); pres.add(k == Data.DOC ? pre : data.parent(pre, k)); } @@ -894,7 +895,7 @@ */ private static boolean updatable(final DBNodes n, final int... no) { if(n == null || (no.length == 0 ? n.size() < 1 : n.size() != 1)) return false; - final int k = n.data.kind(n.pres[0]); + final int k = n.data().kind(n.pre(0)); for(final int i : no) if(k == i) return false; return true; } @@ -915,7 +916,7 @@ * @return function string */ private static String openPre(final DBNodes n, final int i) { - return Function._DB_OPEN_PRE.get(null, null, Str.get(n.data.meta.name), - Int.get(n.pres[i])).toString(); + return Function._DB_OPEN_PRE.get(null, null, Str.get(n.data().meta.name), + Int.get(n.pre(i))).toString(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/GUIOptions.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/GUIOptions.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/GUIOptions.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/GUIOptions.java 2015-07-14 10:54:40.000000000 +0000 @@ -189,7 +189,7 @@ /** Maximum text size to be displayed. */ public static final NumberOption MAXTEXT = new NumberOption("MAXTEXT", 1 << 21); /** Maximum number of hits to be displayed (-1: return all hits; default: 250K). */ - public static final NumberOption MAXHITS = new NumberOption("MAXHITS", 250000); + public static final NumberOption MAXRESULTS = new NumberOption("MAXHITS", 250000); /** Comment: written to options file. */ public static final Comment C_SEARCH = new Comment("Search"); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/layout/BaseXMem.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/layout/BaseXMem.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/layout/BaseXMem.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/layout/BaseXMem.java 2015-07-14 10:54:40.000000000 +0000 @@ -35,8 +35,7 @@ } // regularly refresh panel - final Timer timer = new Timer(true); - timer.scheduleAtFixedRate(new TimerTask() { + new Timer(true).scheduleAtFixedRate(new TimerTask() { @Override public void run() { repaint(); } }, 0, 5000); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/layout/BaseXScrollBar.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/layout/BaseXScrollBar.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/layout/BaseXScrollBar.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/layout/BaseXScrollBar.java 2015-07-14 10:54:40.000000000 +0000 @@ -183,7 +183,7 @@ // start dragging if(sliding || animated) return; - new Thread() { + final Thread t = new Thread() { @Override public void run() { // scroll up/down/move slider @@ -207,7 +207,9 @@ } } } - }.start(); + }; + t.setDaemon(true); + t.start(); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/text/TextEditor.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/text/TextEditor.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/text/TextEditor.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/text/TextEditor.java 2015-07-14 10:54:40.000000000 +0000 @@ -418,16 +418,25 @@ final byte[] ste = concat(st, SPACE), ene = concat(SPACE, en); final int sl = st.length, el = en.length, sle = ste.length, ele = ene.length; - // no selection: select line if(!selected()) { + // no selection: select line start = pos; end = pos; while(start > 0 && text[start - 1] != '\n') --start; while(end < size() && text[end] != '\n') ++end; + } else if(start > end) { + // selection -> start < end + final int s = start; + start = end; + end = s; } - final int min = Math.min(start, end); - int max = Math.max(start, end); + // ignore whitespaces + while(start < end && ws(text[start])) ++start; + while(end > start && ws(text[end - 1])) --end; + + final int min = start; + int max = end; if(selected() && text[max - 1] == '\n') max--; // create new text with or without comment @@ -689,14 +698,14 @@ } else if(ch == '~') { // closes XQuery comments if(prev == ':' && pprv == '(') { - sb.append("\n : \n "); + sb.append(" "); if(curr != ':') { sb.append(':'); if(curr != ')') sb.append(')'); } else if(next != ')') { sb.append(')'); } - move = 5; + move = 2; } } else if(ch == '-') { // closes XML comments @@ -737,20 +746,19 @@ * @param sb string builder */ private void closeElem(final StringBuilder sb) { - int p = pos - 1; - for(; p >= 0; p--) { - final byte b = text[p]; - if(!XMLToken.isNCChar(b) && b != ':') { - if(b == '<' && p < pos - 1) { + final int p = pos; + while(pos > 0) { + final int cp = prev(); + if(!XMLToken.isNCChar(cp) && cp != ':') { + if(cp == '<' && pos < p - 1) { // add closing element - sb.append("'); - break; + next(); + sb.append("'); } - return; + break; } } + pos = p; } /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/explore/ExploreView.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/explore/ExploreView.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/explore/ExploreView.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/explore/ExploreView.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,10 +8,10 @@ import javax.swing.*; -import org.basex.data.*; import org.basex.gui.*; import org.basex.gui.layout.*; import org.basex.gui.view.*; +import org.basex.query.value.seq.*; /** * This view allows the input of database queries. diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/folder/FolderIterator.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/folder/FolderIterator.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/folder/FolderIterator.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/folder/FolderIterator.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,6 +1,7 @@ package org.basex.gui.view.folder; import org.basex.data.*; +import org.basex.query.value.seq.*; import org.basex.util.list.*; /** @@ -94,7 +95,7 @@ private boolean moreCS() { final DBNodes current = view.gui.context.current(); if(current == null || ++cp >= current.size()) return false; - par = current.pres[cp]; + par = current.pre(cp); pre = par; level = 0; ll = 0; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/folder/FolderView.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/folder/FolderView.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/folder/FolderView.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/folder/FolderView.java 2015-07-14 10:54:40.000000000 +0000 @@ -14,6 +14,7 @@ import org.basex.gui.*; import org.basex.gui.layout.*; import org.basex.gui.view.*; +import org.basex.query.value.seq.*; /** * This view offers a folder visualization of the database contents. @@ -113,7 +114,7 @@ scroll.pos(0); final DBNodes curr = gui.context.current(); - if(more && curr.size() != 0) jumpTo(curr.pres[0], true); + if(more && curr.size() != 0) jumpTo(curr.pre(0), true); refreshHeight(); repaint(); } @@ -141,7 +142,7 @@ scroll.pos(0); final DBNodes marked = gui.context.marked; - if(marked.size() != 0) jumpTo(marked.pres[0], true); + if(marked.size() != 0) jumpTo(marked.pre(0), true); refreshHeight(); repaint(); } @@ -461,11 +462,11 @@ // calculate new tree position gui.context.focused = -1; final DBNodes curr = gui.context.current(); - int pre = curr.pres[0]; + int pre = curr.pre(0); final FolderIterator it = new FolderIterator(this); while(it.more() && focus-- != 0) pre = it.pre; - if(pre == curr.pres[0] && down) ++pre; + if(pre == curr.pre(0) && down) ++pre; gui.notify.focus(pre, this); jumpTo(pre, false); repaint(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/map/MapDefault.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/map/MapDefault.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/map/MapDefault.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/map/MapDefault.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,6 +6,7 @@ import org.basex.gui.*; import org.basex.gui.layout.*; import org.basex.gui.view.*; +import org.basex.query.util.ft.*; /** * Adds default paint operations to TreeMap. diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/map/MapPainter.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/map/MapPainter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/map/MapPainter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/map/MapPainter.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,6 +5,7 @@ import org.basex.data.*; import org.basex.gui.*; import org.basex.gui.view.*; +import org.basex.query.value.seq.*; /** * Provides an interface for data specific TreeMap visualizations. @@ -20,12 +21,12 @@ /** * Constructor. - * @param m map reference - * @param opts gui options + * @param map map reference + * @param gopts gui options */ - MapPainter(final MapView m, final GUIOptions opts) { - view = m; - gopts = opts; + MapPainter(final MapView map, final GUIOptions gopts) { + this.view = map; + this.gopts = gopts; } /** @@ -42,7 +43,7 @@ if(p >= 0) { // mark ancestor of invisible node; final int i = rects.find(rects.get(ri)); - return p < marked.size() && i + 1 < rects.size && marked.sorted[p] < + return p < marked.size() && i + 1 < rects.size && marked.sorted(p) < rects.sorted[i + 1].pre ? GUIConstants.colormark2 : null; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/map/MapRect.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/map/MapRect.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/map/MapRect.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/map/MapRect.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,7 @@ package org.basex.gui.view.map; -import org.basex.data.*; import org.basex.gui.view.*; +import org.basex.query.util.ft.*; /** * View rectangle. diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/map/MapRenderer.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/map/MapRenderer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/map/MapRenderer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/map/MapRenderer.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,8 +8,8 @@ import java.util.*; import org.basex.core.*; -import org.basex.data.*; import org.basex.gui.layout.*; +import org.basex.query.util.ft.*; import org.basex.util.ft.*; import org.basex.util.list.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/map/MapView.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/map/MapView.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/map/MapView.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/map/MapView.java 2015-07-14 10:54:40.000000000 +0000 @@ -14,6 +14,7 @@ import org.basex.gui.*; import org.basex.gui.layout.*; import org.basex.gui.view.*; +import org.basex.query.value.seq.*; import org.basex.util.*; import org.basex.util.ft.*; import org.basex.util.list.*; @@ -140,7 +141,7 @@ final int hist = gui.notify.hist; final boolean page = !more && rectHist[hist + 1] != null && rectHist[hist + 1].pre == 0 || more && (context.size() != 1 || - focused == null || context.pres[0] != focused.pre); + focused == null || context.pre(0) != focused.pre); if(page) focused = new MapRect(0, 0, getWidth(), 1); zoom(more, quick); @@ -217,7 +218,9 @@ repaint(); } else { zoomStep = ZOOMSIZE; - new Thread(this).start(); + final Thread t = new Thread(this); + t.setDaemon(true); + t.start(); } } @@ -280,8 +283,8 @@ gui.cursor(CURSORWAIT); initLen(); - layout = new MapLayout(nodes.data, textLen, gui.gopts); - layout.makeMap(rect, new MapList(nodes.pres.clone()), 0, (int) nodes.size() - 1); + layout = new MapLayout(nodes.data(), textLen, gui.gopts); + layout.makeMap(rect, new MapList(nodes.pres().clone()), 0, (int) nodes.size() - 1); // rectangles are copied to avoid synchronization issues mainRects = layout.rectangles.copy(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/plot/PlotAxis.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/plot/PlotAxis.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/plot/PlotAxis.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/plot/PlotAxis.java 2015-07-14 10:54:40.000000000 +0000 @@ -251,7 +251,7 @@ for(int p = pre; p < limit; ++p) { final int kind = data.kind(p); if((kind == Data.ELEM && elem || kind == Data.ATTR && !elem) && - attrID == data.name(p)) return data.atom(p); + attrID == data.nameId(p)) return data.atom(p); } return EMPTY; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/plot/PlotData.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/plot/PlotData.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/plot/PlotData.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/plot/PlotData.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,6 +8,7 @@ import org.basex.data.*; import org.basex.index.name.*; import org.basex.index.stats.*; +import org.basex.query.value.seq.*; import org.basex.util.list.*; /** @@ -96,19 +97,19 @@ final int itmID = data.elemNames.id(item); if(!sub) { - pres = nodes.pres; + pres = nodes.pres(); Arrays.sort(pres); return; } - final int[] contextPres = nodes.pres; + final int[] contextPres = nodes.pres(); for(int p : contextPres) { if(p >= data.meta.size) break; final int nl = p + data.size(p, Data.ELEM); while(p < nl) { final int kind = data.kind(p); if(kind == Data.ELEM) { - if(data.name(p) == itmID) il.add(p); + if(data.nameId(p) == itmID) il.add(p); p += data.attSize(p, Data.ELEM); } else { ++p; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/plot/PlotView.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/plot/PlotView.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/plot/PlotView.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/plot/PlotView.java 2015-07-14 10:54:40.000000000 +0000 @@ -16,6 +16,7 @@ import org.basex.gui.layout.*; import org.basex.gui.view.*; import org.basex.index.stats.*; +import org.basex.query.value.seq.*; import org.basex.util.list.*; /** @@ -300,12 +301,12 @@ if(focused != -1) { final int itmID = data.elemNames.id(plotData.item); int k = data.kind(focused); - int name = data.name(focused); + int name = data.nameId(focused); while(focused > 0 && itmID != name) { focused = data.parent(focused, k); if(focused > -1) { k = data.kind(focused); - name = data.name(focused); + name = data.nameId(focused); } } } @@ -385,7 +386,8 @@ final DBNodes marked = gui.context.marked; if(marked.size() == 0) return; - final int[] m = Arrays.copyOf(marked.pres, marked.pres.length); + final int[] pres = marked.pres(); + final int[] m = Arrays.copyOf(pres, pres.length); int i = 0; // no child nodes of the marked context nodes are marked diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/table/TableContent.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/table/TableContent.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/table/TableContent.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/table/TableContent.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,6 +9,7 @@ import org.basex.data.*; import org.basex.gui.*; import org.basex.gui.layout.*; +import org.basex.query.value.seq.*; import org.basex.util.*; /** @@ -81,7 +82,7 @@ final int pre = tdata.rows.get(l); final long ms = marked.size(); - while(mpos < ms && marked.pres[mpos] < pre) ++mpos; + while(mpos < ms && marked.pre(mpos) < pre) ++mpos; // draw line g.setColor(GUIConstants.color2); @@ -90,7 +91,7 @@ g.drawLine(0, posY + rowH, w, posY + rowH); // verify if current node is marked or focused - final boolean rm = mpos < marked.size() && marked.pres[mpos] == pre; + final boolean rm = mpos < marked.size() && marked.pre(mpos) == pre; final boolean rf = pre == rfocus; final int col = rm ? rf ? 5 : 4 : 3; if(rm || rf) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/table/TableData.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/table/TableData.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/table/TableData.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/table/TableData.java 2015-07-14 10:54:40.000000000 +0000 @@ -164,19 +164,19 @@ private void createRows() { final Data data = ctx.data(); rows = new IntList(); - for(int p : ctx.current().pres) { - if(p >= data.meta.size) break; - final int s = p + data.size(p, data.kind(p)); + for(int pre : ctx.current().pres()) { + if(pre >= data.meta.size) break; + final int s = pre + data.size(pre, data.kind(pre)); // find first root element name do { - if(data.kind(p) == Data.ELEM && data.name(p) == root) break; - } while(++p < s); + if(data.kind(pre) == Data.ELEM && data.nameId(pre) == root) break; + } while(++pre < s); // parse whole document and collect root element names - while(p < s) { - final int k = data.kind(p); - if(k == Data.ELEM && data.name(p) == root) rows.add(p); - p += data.attSize(p, k); + while(pre < s) { + final int k = data.kind(pre); + if(k == Data.ELEM && data.nameId(pre) == root) rows.add(pre); + pre += data.attSize(pre, k); } } sort(); @@ -253,7 +253,7 @@ final int s = p + data.size(p, data.kind(p)); while(p != s) { final int k = data.kind(p); - if((e && k == Data.ELEM || !e && k == Data.ATTR) && data.name(p) == c) { + if((e && k == Data.ELEM || !e && k == Data.ATTR) && data.nameId(p) == c) { tokens[r] = data.atom(p); break; } @@ -276,7 +276,7 @@ if(pre == -1) return -1; int p = pre; int k = data.kind(p); - while(p != -1 && (k != Data.ELEM || data.name(p) != root)) { + while(p != -1 && (k != Data.ELEM || data.nameId(p) != root)) { p = data.parent(p, k); k = p == -1 ? 0 : data.kind(p); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/table/TableHeader.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/table/TableHeader.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/table/TableHeader.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/table/TableHeader.java 2015-07-14 10:54:40.000000000 +0000 @@ -14,6 +14,7 @@ import org.basex.gui.*; import org.basex.gui.layout.*; import org.basex.gui.view.table.TableData.TableCol; +import org.basex.query.value.seq.*; /** * This is the header of the table view. diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/table/TableIterator.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/table/TableIterator.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/table/TableIterator.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/table/TableIterator.java 2015-07-14 10:54:40.000000000 +0000 @@ -44,7 +44,7 @@ */ void init(final int p) { last = p + data.size(p, data.kind(p)); - rootElem = data.name(p); + rootElem = data.nameId(p); pre = p; elem = -1; } @@ -60,7 +60,7 @@ // content found... if(text || k == Data.ATTR) { - final int id = text ? elem : data.name(pre); + final int id = text ? elem : data.nameId(pre); // find correct column... final TableCol[] cols = tdata.cols; final int cl = cols.length; @@ -69,7 +69,7 @@ } } else if(k == Data.ELEM) { // remember name of last element - elem = data.name(pre); + elem = data.nameId(pre); if(elem == rootElem) return false; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/table/TableView.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/table/TableView.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/table/TableView.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/table/TableView.java 2015-07-14 10:54:40.000000000 +0000 @@ -13,6 +13,7 @@ import org.basex.gui.*; import org.basex.gui.layout.*; import org.basex.gui.view.*; +import org.basex.query.value.seq.*; import org.basex.util.*; import org.basex.util.list.*; @@ -79,7 +80,9 @@ } else { if(!more) tdata.resetFilter(); gui.updating = true; - new Thread(this).start(); + final Thread t = new Thread(this); + t.setDaemon(true); + t.start(); } } @@ -96,7 +99,7 @@ final Context context = gui.context; final DBNodes marked = context.marked; if(marked.size() != 0) { - final int p = tdata.getRoot(context.data(), marked.pres[0]); + final int p = tdata.getRoot(context.data(), marked.pre(0)); if(p != -1) setPos(p); } repaint(); @@ -250,7 +253,7 @@ } else { DBNodes nodes = context.marked; if(getCursor() == CURSORARROW) { - nodes = new DBNodes(nodes.data, tdata.getRoot(nodes.data, pre)); + nodes = new DBNodes(nodes.data(), tdata.getRoot(nodes.data(), pre)); } gui.notify.context(nodes, false, null); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/text/TextView.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/text/TextView.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/text/TextView.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/text/TextView.java 2015-07-14 10:54:40.000000000 +0000 @@ -12,7 +12,6 @@ import org.basex.core.*; import org.basex.core.parse.*; -import org.basex.data.*; import org.basex.gui.*; import org.basex.gui.layout.*; import org.basex.gui.layout.BaseXFileChooser.Mode; @@ -22,6 +21,8 @@ import org.basex.io.out.*; import org.basex.io.serial.*; import org.basex.query.*; +import org.basex.query.value.*; +import org.basex.query.value.seq.*; import org.basex.util.*; /** @@ -158,27 +159,27 @@ /** * Caches the output. * @param out cached output - * @param c command - * @param r result + * @param command command + * @param result result * @throws QueryException query exception */ - public void cache(final ArrayOutput out, final Command c, final Result r) + public void cache(final ArrayOutput out, final Command command, final Value result) throws QueryException { // cache command or node set cmd = null; ns = null; - final int mh = gui.context.options.get(MainOptions.MAXHITS); + final int mh = gui.gopts.get(GUIOptions.MAXRESULTS); boolean parse = false; - if(mh >= 0 && r != null && r.size() >= mh) { + if(mh >= 0 && result != null && result.size() >= mh) { parse = true; } else if(out.finished()) { - if(r instanceof DBNodes) ns = (DBNodes) r; + if(result instanceof DBNodes) ns = (DBNodes) result; else parse = true; } // create new command instance - if(parse) cmd = new CommandParser(c.toString(), gui.context).parseSingle(); + if(parse) cmd = new CommandParser(command.toString(), gui.context).parseSingle(); } /** @@ -209,11 +210,6 @@ gui.gopts.set(GUIOptions.WORKPATH, file.path()); gui.cursor(CURSORWAIT, true); - final MainOptions opts = gui.context.options; - final int mh = opts.get(MainOptions.MAXHITS); - opts.set(MainOptions.MAXHITS, -1); - opts.set(MainOptions.CACHEQUERY, false); - try(final PrintOutput out = new PrintOutput(file.toString())) { if(cmd != null) { cmd.execute(gui.context, out); @@ -225,8 +221,6 @@ } catch(final IOException ex) { BaseXDialog.error(gui, Util.info(FILE_NOT_SAVED_X, file)); } finally { - opts.set(MainOptions.MAXHITS, mh); - opts.set(MainOptions.CACHEQUERY, true); gui.cursor(CURSORARROW, true); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/tree/TreeRects.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/tree/TreeRects.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/tree/TreeRects.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/tree/TreeRects.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,9 +2,9 @@ import java.awt.*; -import org.basex.data.*; import org.basex.gui.layout.*; import org.basex.gui.view.*; +import org.basex.query.value.seq.*; /** * This class stores the rectangles. @@ -40,7 +40,7 @@ double generateRects(final TreeSubtree sub, final Graphics g, final int ds, final int dw, final boolean slim) { - final int[] roots = nodes.pres; + final int[] roots = nodes.pres(); final int rl = roots.length; if(rl == 0) return 0; final double w = (dw - BORDER_PADDING - ds) / (double) rl; @@ -155,7 +155,7 @@ * @return text */ byte[] getText(final int pre) { - return ViewData.name(view.gui.gopts, nodes.data, pre); + return ViewData.name(view.gui.gopts, nodes.data(), pre); } /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/tree/TreeView.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/tree/TreeView.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/tree/TreeView.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/tree/TreeView.java 2015-07-14 10:54:40.000000000 +0000 @@ -15,6 +15,7 @@ import org.basex.gui.*; import org.basex.gui.layout.*; import org.basex.gui.view.*; +import org.basex.query.value.seq.*; import org.basex.util.*; import org.basex.util.list.*; @@ -161,7 +162,7 @@ gui.painting = true; final DBNodes nodes = gui.context.current(); - roots = nodes.pres; + roots = nodes.pres(); final int rl = roots.length; if(rl == 0) return; @@ -547,15 +548,15 @@ BaseXLayout.antiAlias(mg); mg.setFont(font); - final int[] mark = gui.context.marked.pres; - if(mark.length == 0) return; + final int[] marked = gui.context.marked.pres(); + if(marked.length == 0) return; int rn = 0; final int rl = roots.length; while(rn < rl) { - final int ml = mark.length; + final int ml = marked.length; final LinkedList marklink = new LinkedList<>(); - for(int m = 0; m < ml; ++m) marklink.add(m, mark[m]); + for(int m = 0; m < ml; ++m) marklink.add(m, marked[m]); for(int lv = 0; lv < sub.subtreeHeight(rn); ++lv) { final int y = getYperLevel(lv); @@ -570,8 +571,7 @@ if(ix > -1) { li.remove(); - final int x = (int) (rect.w * ix / (double) sub.levelSize(rn, - lv)); + final int x = (int) (rect.w * ix / (double) sub.levelSize(rn, lv)); mg.setColor(colormark1); mg.fillRect(rect.x + x, y, 2, nodeHeight + 1); } @@ -579,9 +579,7 @@ } else { while(li.hasNext()) { final int pre = li.next(); - final TreeRect rect = tr.searchRect(sub, rn, lv, pre); - if(rect != null) { li.remove(); drawRectangle(mg, rn, lv, rect, pre, Draw.MARK); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/ViewNotifier.java basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/ViewNotifier.java --- basex-8.1.1/basex-core/src/main/java/org/basex/gui/view/ViewNotifier.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/gui/view/ViewNotifier.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,6 +8,7 @@ import org.basex.data.*; import org.basex.gui.*; import org.basex.gui.layout.*; +import org.basex.query.value.seq.*; import org.basex.util.*; /** @@ -169,10 +170,10 @@ // add new entry if current node set has not been cached yet final DBNodes newn = nodes.discardDocs(); - final DBNodes empty = new DBNodes(ctx.data(), ctx.marked.ftpos()); + final DBNodes empty = new DBNodes(ctx.data()).ftpos(ctx.marked.ftpos()); final DBNodes curr = quick ? ctx.current() : null; final DBNodes cmp = quick ? curr : ctx.marked; - if(cont[hist] == null ? cmp != null : cmp == null || !cont[hist].equals(cmp)) { + if(cont[hist] == null ? cmp != null : cmp == null || !cont[hist].sameAs(cmp)) { checkHist(); if(quick) { // store history entry diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/index/ft/FTIndex.java basex-8.2.3/basex-core/src/main/java/org/basex/index/ft/FTIndex.java --- basex-8.1.1/basex-core/src/main/java/org/basex/index/ft/FTIndex.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/index/ft/FTIndex.java 2015-07-14 10:54:40.000000000 +0000 @@ -12,8 +12,10 @@ import org.basex.index.*; import org.basex.index.query.*; import org.basex.index.stats.*; +import org.basex.index.value.*; import org.basex.io.random.*; import org.basex.query.expr.ft.*; +import org.basex.query.util.ft.*; import org.basex.util.*; import org.basex.util.ft.*; import org.basex.util.hash.*; @@ -46,7 +48,7 @@ * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ -public final class FTIndex implements Index { +public final class FTIndex implements ValueIndex { /** Entry size. */ private static final int ENTRY = 9; @@ -103,9 +105,6 @@ } @Override - public synchronized void init() { } - - @Override public synchronized int costs(final IndexToken it) { final byte[] tok = it.get(); if(tok.length > data.meta.maxlen) return Integer.MAX_VALUE; @@ -478,4 +477,16 @@ pos = ps; } } + + @Override + public void add(final TokenObjMap map) { } + + @Override + public void delete(final TokenObjMap map) { } + + @Override + public void replace(final byte[] old, final byte[] key, final int id) { } + + @Override + public void flush() { } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/index/IndexBuilder.java basex-8.2.3/basex-core/src/main/java/org/basex/index/IndexBuilder.java --- basex-8.1.1/basex-core/src/main/java/org/basex/index/IndexBuilder.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/index/IndexBuilder.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,6 +6,7 @@ import org.basex.core.*; import org.basex.data.*; +import org.basex.index.value.*; import org.basex.util.*; /** @@ -52,7 +53,7 @@ * @return index instance * @throws IOException I/O Exception */ - public abstract Index build() throws IOException; + public abstract ValueIndex build() throws IOException; /** * Checks if the command was interrupted, and prints some debug output. diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/index/Index.java basex-8.2.3/basex-core/src/main/java/org/basex/index/Index.java --- basex-8.1.1/basex-core/src/main/java/org/basex/index/Index.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/index/Index.java 2015-07-14 10:54:40.000000000 +0000 @@ -12,11 +12,6 @@ */ public interface Index { /** - * Initializes the index. - */ - void init(); - - /** * Returns information on the index structure. * @param options main options * @return info @@ -46,13 +41,13 @@ int costs(final IndexToken token); /** - * Closes the index. - */ - void close(); - - /** * Drops the index. * @return success flag */ boolean drop(); + + /** + * Closes the index. + */ + void close(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/index/name/Names.java basex-8.2.3/basex-core/src/main/java/org/basex/index/name/Names.java --- basex-8.1.1/basex-core/src/main/java/org/basex/index/name/Names.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/index/name/Names.java 2015-07-14 10:54:40.000000000 +0000 @@ -47,7 +47,9 @@ for(int s = 1; s < size; ++s) stats[s] = new Stats(in); } - @Override + /** + * Initializes the index. + */ public void init() { for(int s = 1; s < size; ++s) stats[s] = new Stats(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/index/path/PathSummary.java basex-8.2.3/basex-core/src/main/java/org/basex/index/path/PathSummary.java --- basex-8.1.1/basex-core/src/main/java/org/basex/index/path/PathSummary.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/index/path/PathSummary.java 2015-07-14 10:54:40.000000000 +0000 @@ -75,7 +75,9 @@ data = dt; } - @Override + /** + * Initializes the index. + */ public void init() { root = new PathNode(); stack.clear(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/index/query/FTIndexIterator.java basex-8.2.3/basex-core/src/main/java/org/basex/index/query/FTIndexIterator.java --- basex-8.1.1/basex-core/src/main/java/org/basex/index/query/FTIndexIterator.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/index/query/FTIndexIterator.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,6 +1,6 @@ package org.basex.index.query; -import org.basex.data.*; +import org.basex.query.util.ft.*; /** * This interface provides methods for returning index results. diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/index/resource/Docs.java basex-8.2.3/basex-core/src/main/java/org/basex/index/resource/Docs.java --- basex-8.1.1/basex-core/src/main/java/org/basex/index/resource/Docs.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/index/resource/Docs.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,7 +6,6 @@ import java.io.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.io.in.DataInput; import org.basex.io.out.DataOutput; import org.basex.util.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/index/resource/Resources.java basex-8.2.3/basex-core/src/main/java/org/basex/index/resource/Resources.java --- basex-8.1.1/basex-core/src/main/java/org/basex/index/resource/Resources.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/index/resource/Resources.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,7 +4,6 @@ import org.basex.core.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.index.*; import org.basex.index.query.*; import org.basex.io.in.DataInput; @@ -60,10 +59,6 @@ return docs.docs(); } - @Override - public synchronized void init() { - } - /** * Adds entries to the index and updates subsequent nodes. * @param pre insertion position diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/index/value/DiskValues.java basex-8.2.3/basex-core/src/main/java/org/basex/index/value/DiskValues.java --- basex-8.1.1/basex-core/src/main/java/org/basex/index/value/DiskValues.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/index/value/DiskValues.java 2015-07-14 10:54:40.000000000 +0000 @@ -24,7 +24,7 @@ * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ -public class DiskValues implements Index { +public class DiskValues implements ValueIndex { /** ID references. */ final DataAccess idxr; /** ID lists. */ @@ -69,9 +69,6 @@ } @Override - public void init() { } - - @Override public byte[] info(final MainOptions options) { final TokenBuilder tb = new TokenBuilder(); tb.add(LI_STRUCTURE).add(SORTED_LIST).add(NL); @@ -120,27 +117,13 @@ } } - /** - * Add entries to the index. - * @param map a set of [key, id-list] pairs - */ - @SuppressWarnings("unused") + @Override public void add(final TokenObjMap map) { } - /** - * Deletes index entries from the index. - * @param map a set of [key, id-list] pairs - */ - @SuppressWarnings("unused") + @Override public void delete(final TokenObjMap map) { } - /** - * Replaces an index entry in the index. - * @param old old record key - * @param key new record key - * @param id record id - */ - @SuppressWarnings("unused") + @Override public void replace(final byte[] old, final byte[] key, final int id) { } @Override @@ -151,9 +134,7 @@ return keysFrom(key, input.descending); } - /** - * Flushes the buffered data. - */ + @Override public final void flush() { idxl.flush(); idxr.flush(); @@ -504,10 +485,9 @@ tb.add("- references:").add("\n"); for(int m = 0; m < sz; m++) { final long pos = idxr.read5(m * 5L); - final int oc = idxl.readNum(pos); - int id = idxl.readNum(); - tb.add(" ").addInt(m).add(". key: \"").add(data.text(pre(id), text)).add("\"; offset: "); - tb.addLong(pos).add("; id/dists: ").addInt(id).add('/').addInt(pre(id)); + int oc = idxl.readNum(pos), id = idxl.readNum(), pre = pre(id); + tb.add(" ").addInt(m).add(". offset: ").addLong(pos).add(", key: \""); + tb.add(data.text(pre, text)).add("\", ids/pres: ").addInt(id).add('/').addInt(pre); for(int n = 1; n < oc; n++) { id += idxl.readNum(); tb.add(",").addInt(id).add('/').addInt(pre(id)); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/index/value/MemValues.java basex-8.2.3/basex-core/src/main/java/org/basex/index/value/MemValues.java --- basex-8.1.1/basex-core/src/main/java/org/basex/index/value/MemValues.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/index/value/MemValues.java 2015-07-14 10:54:40.000000000 +0000 @@ -12,6 +12,7 @@ import org.basex.index.stats.*; import org.basex.util.*; import org.basex.util.hash.*; +import org.basex.util.list.*; /** * This class provides main memory access to attribute values and text contents. @@ -19,7 +20,7 @@ * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ -public final class MemValues extends TokenSet implements Index { +public final class MemValues extends TokenSet implements ValueIndex { /** Updating index. */ private final boolean updindex; /** Indexing flag. */ @@ -42,9 +43,6 @@ } @Override - public void init() { } - - @Override public IndexIterator iter(final IndexToken token) { final byte k = token.type() == IndexType.TEXT ? Data.TEXT : Data.ATTR; final int i = id(token.get()); @@ -137,6 +135,18 @@ } @Override + public void add(final TokenObjMap map) { } + + @Override + public void delete(final TokenObjMap map) { } + + @Override + public void replace(final byte[] old, final byte[] key, final int id) { } + + @Override + public void flush() { } + + @Override public void close() { } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/index/value/UpdatableDiskValues.java basex-8.2.3/basex-core/src/main/java/org/basex/index/value/UpdatableDiskValues.java --- basex-8.1.1/basex-core/src/main/java/org/basex/index/value/UpdatableDiskValues.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/index/value/UpdatableDiskValues.java 2015-07-14 10:54:40.000000000 +0000 @@ -54,8 +54,9 @@ c += idxl.readNum(); il.add(c); } - // write new ids + // mark old slot as empty free.add((int) (idxl.cursor() - off), off); + // write new ids writeIds(key, il.add(ids), index++); } else { index = -(index + 1); @@ -121,7 +122,7 @@ // create space for new entry final int sz = size() + 1; final byte[] tmp = idxr.readBytes(0, sz * 5); - for(int i = sz - 1; i > index; --i) copy(tmp, i, i - 1); + for(int i = sz - 1; i > index; --i) copy(tmp, i - 1, i); idxr.cursor(0); idxr.writeBytes(tmp, 0, sz * 5); size(sz); @@ -129,24 +130,18 @@ } else { // add id to the existing id list final long off = idxr.read5(index * 5L); - final int num = idxl.readNum(off); - final int newSize = num + 1; - newIds = new IntList(newSize); + final int count = idxl.readNum(off); + newIds = new IntList(count + 1); boolean notadded = true; - int prevId = 0; - for(int i = 0; i < num; ++i) { - int v = idxl.readNum(); - if(notadded && id < prevId + v) { + for(int c = 0, currId = 0; c < count; ++c) { + currId += idxl.readNum(); + if(notadded && id < currId) { // add the new id newIds.add(id); notadded = false; - // decrement the difference to the next id - v -= id - prevId; - prevId = id; } - newIds.add(id); - prevId += v; + newIds.add(currId); } if(notadded) newIds.add(id); } @@ -277,6 +272,6 @@ @Override public String toString() { - return super.toString() + "FREE BLOCKS: " + free; + return super.toString() + free; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/index/value/ValueIndex.java basex-8.2.3/basex-core/src/main/java/org/basex/index/value/ValueIndex.java --- basex-8.1.1/basex-core/src/main/java/org/basex/index/value/ValueIndex.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/index/value/ValueIndex.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,39 @@ +package org.basex.index.value; + +import org.basex.index.*; +import org.basex.util.hash.*; +import org.basex.util.list.*; + +/** + * This class provides index-supported access to values + * (attribute values, text contents, full-texts). + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public interface ValueIndex extends Index { + /** + * Add entries to the index. + * @param map a set of [key, id-list] pairs + */ + void add(final TokenObjMap map); + + /** + * Deletes index entries from the index. + * @param map a set of [key, id-list] pairs + */ + void delete(final TokenObjMap map); + + /** + * Replaces an index entry in the index. + * @param old old record key + * @param key new record key + * @param id record id + */ + void replace(final byte[] old, final byte[] key, final int id); + + /** + * Flushes the buffered data. + */ + void flush(); +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/IOFile.java basex-8.2.3/basex-core/src/main/java/org/basex/io/IOFile.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/IOFile.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/IOFile.java 2015-07-14 10:54:40.000000000 +0000 @@ -109,7 +109,7 @@ @Override public InputSource inputSource() { - return new InputSource(pth); + return new InputSource(url()); } @Override @@ -187,7 +187,7 @@ } /** - * Returns the relative paths of all descendant files. + * Returns the relative paths of all descendant files (excluding directories). * @return relative paths */ public synchronized StringList descendants() { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/csv/CsvMapConverter.java basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/csv/CsvMapConverter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/csv/CsvMapConverter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/csv/CsvMapConverter.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,7 +4,7 @@ import org.basex.build.csv.*; import org.basex.query.*; -import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.item.*; import org.basex.query.value.map.Map; @@ -16,9 +16,9 @@ */ final class CsvMapConverter extends CsvConverter { /** All records. */ - private final ArrayList records = new ArrayList<>(1); + private final ArrayList records = new ArrayList<>(1); /** Current record. */ - private ValueBuilder record = new ValueBuilder(); + private ItemList record = new ItemList(); /** * Constructor. @@ -35,7 +35,7 @@ @Override protected void record() { - record = new ValueBuilder(); + record = new ItemList(); if(!headers.isEmpty()) record.add(Map.EMPTY); records.add(record); col = 0; @@ -61,8 +61,8 @@ try { Map map = Map.EMPTY; int row = 1; - for(final ValueBuilder vb : records) { - map = map.put(Int.get(row++), vb.value(), null); + for(final ItemList list : records) { + map = map.put(Int.get(row++), list.value(), null); } return map; } catch(final QueryException ex) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonAttsConverter.java basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonAttsConverter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonAttsConverter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonAttsConverter.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,6 @@ package org.basex.io.parse.json; import static org.basex.io.parse.json.JsonConstants.*; -import static org.basex.util.Token.*; import org.basex.build.json.*; import org.basex.query.value.node.*; @@ -31,16 +30,18 @@ } @Override - void openPair(final byte[] name) { - final FElem e = new FElem(PAIR).add(NAME, name); - curr.add(e); - curr = e; - nm = name; + void openPair(final byte[] name, final boolean add) { + if(add) { + final FElem e = new FElem(PAIR).add(NAME, name); + curr.add(e); + curr = e; + nm = name; + } } @Override void closePair(final boolean add) { - curr = (FElem) curr.parent(); + if(add) curr = (FElem) curr.parent(); } @Override @@ -70,30 +71,6 @@ } @Override - public void openConstr(final byte[] name) { - openObject(); - openPair(name); - openArray(); - } - - @Override - public void openArg() { - openItem(); - } - - @Override - public void closeArg() { - closeItem(); - } - - @Override - public void closeConstr() { - closeArray(); - closePair(true); - closeObject(); - } - - @Override public void numberLit(final byte[] value) { addType(NUMBER).add(value); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonBasicConverter.java basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonBasicConverter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonBasicConverter.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonBasicConverter.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,147 @@ +package org.basex.io.parse.json; + +import static org.basex.io.parse.json.JsonConstants.*; +import static org.basex.query.QueryError.*; +import static org.basex.util.Token.*; + +import org.basex.build.json.*; +import org.basex.build.json.JsonParserOptions.JsonDuplicates; +import org.basex.query.*; +import org.basex.query.value.node.*; +import org.basex.util.list.*; + +/** + *

    This class converts a JSON document to XML.

    + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class JsonBasicConverter extends JsonXmlConverter { + /** Add pairs. */ + private final BoolList addPairs = new BoolList(); + /** Unescape characters. */ + private final boolean unescape; + /** Name of next element. */ + private byte[] name; + + /** + * Constructor. + * @param opts json options + * @throws QueryIOException query I/O exception + */ + JsonBasicConverter(final JsonParserOptions opts) throws QueryIOException { + super(opts); + unescape = jopts.get(JsonParserOptions.UNESCAPE); + addPairs.add(true); + final JsonDuplicates dupl = jopts.get(JsonParserOptions.DUPLICATES); + if(dupl == JsonDuplicates.USE_LAST) throw new QueryIOException( + BXJS_INVALID_X.get(null, JsonParserOptions.DUPLICATES.name(), dupl)); + } + + @Override + void openObject() { + open(MAP); + } + + @Override + void openPair(final byte[] key, final boolean add) { + name = key; + addPairs.add(add() && add); + } + + @Override + void closePair(final boolean add) { + addPairs.pop(); + } + + @Override + void closeObject() { + close(); + } + + @Override + void openArray() { + open(ARRAY); + } + + @Override + void openItem() { } + + @Override + void closeItem() { } + + @Override + void closeArray() { + close(); + } + + @Override + public void numberLit(final byte[] value) { + if(add()) addElem(NUMBER).add(value); + } + + @Override + public void stringLit(final byte[] value) { + if(add()) { + final FElem e = addElem(STRING).add(value); + if(!unescape && contains(value, '\\')) e.add(ESCAPED, TRUE); + } + } + + @Override + public void nullLit() { + if(add()) addElem(NULL); + } + + @Override + public void booleanLit(final byte[] value) { + if(add()) addElem(BOOLEAN).add(value); + } + + /** + * Adds a new element with the given type. + * @param type JSON type + * @return new element + */ + private FElem addElem(final byte[] type) { + final FElem e = new FElem(type, QueryText.FN_URI); + // root node: declare namespace + if(curr == null) e.declareNS(); + + if(name != null) { + e.add(KEY, name); + if(!unescape && contains(name, '\\')) e.add(ESCAPED_KEY, TRUE); + name = null; + } + + if(curr != null) curr.add(e); + else curr = e; + return e; + } + + /** + * Opens an entry. + * @param type JSON type + */ + private void open(final byte[] type) { + if(add()) curr = addElem(type); + } + + /** + * Closes an entry. + */ + private void close() { + if(add()) { + final FElem par = (FElem) curr.parent(); + if(par != null) curr = par; + } + } + + /** + * Indicates if an entry should be added. + * @return result of check + */ + private boolean add() { + return addPairs.peek(); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonConstants.java basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonConstants.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonConstants.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonConstants.java 2015-07-14 10:54:40.000000000 +0000 @@ -15,6 +15,12 @@ byte[] TYPE = token("type"); /** Token: item. */ byte[] ITEM = token("item"); + /** Token: key. */ + byte[] KEY = token("key"); + /** Token: escaped. */ + byte[] ESCAPED = token("escaped"); + /** Token: escaped-key. */ + byte[] ESCAPED_KEY = token("escaped-key"); /** Token: string. */ byte[] STRING = token("string"); @@ -22,10 +28,16 @@ byte[] NUMBER = token("number"); /** Token: boolean. */ byte[] BOOLEAN = token("boolean"); + /** Token: null. */ + byte[] NULL = token("null"); /** Token: array. */ byte[] ARRAY = token("array"); /** Token: object. */ byte[] OBJECT = token("object"); + /** Token: map. */ + byte[] MAP = token("map"); + /** Allowed elements. */ + byte[][] ELEMENTS = { STRING, NUMBER, BOOLEAN, NULL, MAP }; /** Token: pair. */ byte[] PAIR = token("pair"); @@ -36,6 +48,8 @@ /** Supported data types. */ byte[][] TYPES = { OBJECT, ARRAY, STRING, NUMBER, BOOLEAN, NULL }; + /** Escape characters. */ + byte[] ESCAPES = token("\"\\/bfnrtu"); /** Plural. */ byte[] S = { 's' }; /** Global data type attributes. */ diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonConverter.java basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonConverter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonConverter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonConverter.java 2015-07-14 10:54:40.000000000 +0000 @@ -66,12 +66,14 @@ * Returns a JSON converter for the given configuration. * @param jopts options * @return JSON converter + * @throws QueryIOException query I/O exception */ - public static JsonConverter get(final JsonParserOptions jopts) { + public static JsonConverter get(final JsonParserOptions jopts) throws QueryIOException { switch(jopts.get(JsonOptions.FORMAT)) { case JSONML: return new JsonMLConverter(jopts); case ATTRIBUTES: return new JsonAttsConverter(jopts); case MAP: return new JsonMapConverter(jopts); + case BASIC: return new JsonBasicConverter(jopts); default: return new JsonDirectConverter(jopts); } } @@ -85,9 +87,10 @@ /** * Called when a pair of a JSON object is opened. * @param key the key of the entry + * @param add add pair * @throws QueryIOException query exception */ - abstract void openPair(byte[] key) throws QueryIOException; + abstract void openPair(byte[] key, boolean add) throws QueryIOException; /** * Called when a pair of a JSON object is closed. @@ -125,30 +128,6 @@ abstract void closeArray() throws QueryIOException; /** - * Called when a constructor function is opened. - * @param name name of the constructor - * @throws QueryIOException query exception - */ - abstract void openConstr(byte[] name) throws QueryIOException; - - /** - * Called when an argument of a constructor function is opened. - */ - abstract void openArg(); - - /** - * Called when an argument of a constructor function is closed. - * @throws QueryIOException query exception - */ - abstract void closeArg() throws QueryIOException; - - /** - * Called when a constructor function is closed. - * @throws QueryIOException query exception - */ - abstract void closeConstr() throws QueryIOException; - - /** * Called when a number literal is encountered. * @param value string representation of the number literal * @throws QueryIOException query exception diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonDirectConverter.java basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonDirectConverter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonDirectConverter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonDirectConverter.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,6 @@ package org.basex.io.parse.json; import static org.basex.io.parse.json.JsonConstants.*; -import static org.basex.util.Token.*; import org.basex.build.json.*; import org.basex.query.value.node.*; @@ -64,28 +63,13 @@ lax = jopts.get(JsonOptions.LAX); } - /** - * Adds a new element with the given type. - * @param type JSON type - * @return the element - */ - private FElem addElem(final byte[] type) { - final FElem e = new FElem(name); - addType(e, e.name(), type); - - if(curr != null) curr.add(e); - else curr = e; - name = null; - return e; - } - @Override void openObject() { curr = addElem(OBJECT); } @Override - void openPair(final byte[] key) { + void openPair(final byte[] key, final boolean add) { name = XMLToken.encode(key, lax); } @@ -117,31 +101,6 @@ } @Override - public void openConstr(final byte[] nm) { - // [LW] what can be done here? - openObject(); - openPair(nm); - openArray(); - } - - @Override - public void openArg() { - openItem(); - } - - @Override - public void closeArg() { - closeItem(); - } - - @Override - public void closeConstr() { - closeArray(); - closePair(true); - closeObject(); - } - - @Override public void numberLit(final byte[] value) { addElem(NUMBER).add(value); } @@ -160,4 +119,19 @@ public void booleanLit(final byte[] value) { addElem(BOOLEAN).add(value); } + + /** + * Adds a new element with the given type. + * @param type JSON type + * @return the element + */ + private FElem addElem(final byte[] type) { + final FElem e = new FElem(name); + addType(e, e.name(), type); + + if(curr != null) curr.add(e); + else curr = e; + name = null; + return e; + } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonFallback.java basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonFallback.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonFallback.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonFallback.java 2015-07-14 10:54:40.000000000 +0000 @@ -12,5 +12,5 @@ * @param string input string * @return converted token */ - public abstract byte[] convert(final byte[] string); + public abstract String convert(final String string); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonMapConverter.java basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonMapConverter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonMapConverter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonMapConverter.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,8 +1,11 @@ package org.basex.io.parse.json; +import static org.basex.query.QueryError.*; + import java.util.*; import org.basex.build.json.*; +import org.basex.build.json.JsonParserOptions.JsonDuplicates; import org.basex.query.*; import org.basex.query.util.list.*; import org.basex.query.value.*; @@ -43,9 +46,13 @@ /** * Constructor. * @param opts json options + * @throws QueryIOException query I/O exception */ - JsonMapConverter(final JsonParserOptions opts) { + JsonMapConverter(final JsonParserOptions opts) throws QueryIOException { super(opts); + final JsonDuplicates dupl = jopts.get(JsonParserOptions.DUPLICATES); + if(dupl == JsonDuplicates.RETAIN) throw new QueryIOException( + BXJS_INVALID_X.get(null, JsonParserOptions.DUPLICATES.name(), dupl)); } @Override @@ -60,7 +67,7 @@ } @Override - void openPair(final byte[] key) { + void openPair(final byte[] key, final boolean add) { stack.push(Str.get(key)); } @@ -102,28 +109,6 @@ } @Override - public void openConstr(final byte[] name) { - openObject(); - openPair(name); - openArray(); - } - - @Override public void openArg() { - openItem(); - } - - @Override public void closeArg() throws QueryIOException { - closeItem(); - } - - @Override - public void closeConstr() throws QueryIOException { - closeArray(); - closePair(true); - closeObject(); - } - - @Override public void numberLit(final byte[] value) throws QueryIOException { try { stack.push(Dbl.get(value, null)); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonMLConverter.java basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonMLConverter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonMLConverter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonMLConverter.java 2015-07-14 10:54:40.000000000 +0000 @@ -67,7 +67,7 @@ } @Override - public void openPair(final byte[] key) throws QueryIOException { + public void openPair(final byte[] key, final boolean add) throws QueryIOException { attName = check(key); } @@ -150,15 +150,4 @@ public void booleanLit(final byte[] b) throws QueryIOException { error("No booleans allowed"); } - - @Override - public void openConstr(final byte[] nm) throws QueryIOException { - error("No constructor functions allowed"); - } - - @Override public void openArg() { } - - @Override public void closeArg() { } - - @Override public void closeConstr() { } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonParser.java basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonParser.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/parse/json/JsonParser.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/parse/json/JsonParser.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,6 +4,7 @@ import static org.basex.util.Token.*; import org.basex.build.json.*; +import org.basex.build.json.JsonOptions.*; import org.basex.build.json.JsonParserOptions.JsonDuplicates; import org.basex.query.*; import org.basex.util.*; @@ -48,7 +49,9 @@ super(in); liberal = opts.get(JsonParserOptions.LIBERAL); unescape = opts.get(JsonParserOptions.UNESCAPE); - duplicates = opts.get(JsonParserOptions.DUPLICATES); + final JsonDuplicates dupl = opts.get(JsonParserOptions.DUPLICATES); + duplicates = dupl != null ? dupl : opts.get(JsonOptions.FORMAT) == JsonFormat.BASIC ? + JsonDuplicates.RETAIN : JsonDuplicates.USE_FIRST; this.conv = conv; } @@ -73,6 +76,7 @@ * @throws QueryIOException query I/O exception */ private void parse() throws QueryIOException { + consume('\uFEFF'); skipWs(); value(); if(more()) throw error("Unexpected trailing content: %", rest()); @@ -114,7 +118,6 @@ if(consume("true")) conv.booleanLit(TRUE); else if(consume("false")) conv.booleanLit(FALSE); else if(consume("null")) conv.nullLit(); - else if(liberal && consume("new") && Character.isWhitespace(curr())) constr(); else throw error("Unexpected JSON value: '%'", rest()); skipWs(); } @@ -135,10 +138,11 @@ if(dupl && duplicates == JsonDuplicates.REJECT) throw error(BXJS_DUPLICATE_X, "Key '%' occurs more than once.", key); - conv.openPair(key); + final boolean add = !(dupl && duplicates == JsonDuplicates.USE_FIRST); + conv.openPair(key, add); consumeWs(':', true); value(); - conv.closePair(!dupl || duplicates == JsonDuplicates.USE_LAST); + conv.closePair(add); set.put(key); } while(consumeWs(',', false) && !(liberal && curr() == '}')); consumeWs('}', true); @@ -165,30 +169,6 @@ } /** - * Parses a JSON constructor function. - * @throws QueryIOException query I/O exception - */ - private void constr() throws QueryIOException { - skipWs(); - if(!input.substring(pos).matches("^[a-zA-Z0-9_-]+\\(.*")) - throw error("Wrong constructor syntax: '%'", rest()); - - final int p = input.indexOf('(', pos); - conv.openConstr(token(input.substring(pos, p))); - pos = p + 1; - skipWs(); - if(!consumeWs(')', false)) { - do { - conv.openArg(); - value(); - conv.closeArg(); - } while(consumeWs(',', false)); - consumeWs(')', true); - } - conv.closeConstr(); - } - - /** * Reads an unquoted string literal. * @return the string * @throws QueryIOException query I/O exception @@ -295,7 +275,7 @@ final int p = pos; int ch = consume(); if(ch == '"') { - if(high != 0) tb.add(INVALID); + if(high != 0) add(high, pos - 7, p); skipWs(); return tb.toArray(); } @@ -362,14 +342,14 @@ if(high != 0) { if(ch >= 0xDC00 && ch <= 0xDFFF) ch = (high - 0xD800 << 10) + ch - 0xDC00 + 0x10000; - else tb.add(INVALID); + else add(high, p, pos); high = 0; } if(ch >= 0xD800 && ch <= 0xDBFF) { high = (char) ch; } else { - add(ch, p); + add(ch, p, pos); } } throw eof(" in string literal"); @@ -378,15 +358,16 @@ /** * Adds the specified character. * @param ch character - * @param p start position of unescape sequence + * @param s start position of invalid unicode sequence + * @param e end position */ - private void add(final int ch, final int p) { + private void add(final int ch, final int s, final int e) { if(XMLToken.valid(ch)) { tb.add(ch); } else if(conv.fallback == null) { tb.add(INVALID); } else { - tb.add(conv.fallback.convert(new TokenBuilder().add(input.substring(p, pos)).finish())); + tb.add(conv.fallback.convert(input.substring(s, e))); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/AdaptiveSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/AdaptiveSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/AdaptiveSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/AdaptiveSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -51,6 +51,13 @@ } @Override + public Serializer sc(final StaticContext sctx) { + xml.sc(sctx); + json.sc(sctx); + return super.sc(sctx); + } + + @Override public void serialize(final Item item) throws IOException { if(more) xml.printChars(itemsep); super.serialize(item); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/BuilderSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/BuilderSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/BuilderSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/BuilderSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,7 +5,7 @@ import java.io.*; import org.basex.build.*; -import org.basex.data.*; +import org.basex.query.util.ft.*; import org.basex.util.*; /** @@ -42,14 +42,14 @@ @Override protected final void finishOpen() throws IOException { - builder.openElem(elem, atts, nsp); + builder.openElem(elem.string(), atts, nsp); atts.clear(); nsp.clear(); } @Override protected void finishEmpty() throws IOException { - builder.emptyElem(elem, atts, nsp); + builder.emptyElem(elem.string(), atts, nsp); atts.clear(); nsp.clear(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/csv/CsvDirectSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/csv/CsvDirectSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/csv/CsvDirectSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/csv/CsvDirectSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,9 +7,10 @@ import org.basex.build.csv.*; import org.basex.build.csv.CsvOptions.CsvFormat; -import org.basex.data.*; import org.basex.io.out.*; import org.basex.io.serial.*; +import org.basex.query.util.ft.*; +import org.basex.query.value.item.*; import org.basex.util.*; import org.basex.util.hash.*; import org.basex.util.list.*; @@ -52,7 +53,7 @@ } @Override - protected void startOpen(final byte[] name) { + protected void startOpen(final QNm name) { if(level == 1) data = new TokenMap(); attv = null; } @@ -106,7 +107,7 @@ */ private void cache(final byte[] value) throws IOException { if(headers != null) { - final byte[] key = atts && attv != null ? attv : elem; + final byte[] key = atts && attv != null ? attv : elem.string(); final byte[] name = XMLToken.decode(key, lax); if(name == null) error("Invalid element name <%>", key); if(!headers.contains(name)) headers.add(name); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/dot/DOTData.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/dot/DOTData.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/dot/DOTData.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/dot/DOTData.java 2015-07-14 10:54:40.000000000 +0000 @@ -78,7 +78,7 @@ // green { "009900", GFLWOR.class }, { "339933", VarStack.class }, - { "33CC33", For.class, Let.class, List.class, Range.class, Context.class, + { "33CC33", For.class, Let.class, List.class, Range.class, ContextValue.class, QueryText.RET }, { "66CC66", Var.class, Cast.class }, // cyan diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/dot/DOTSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/dot/DOTSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/dot/DOTSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/dot/DOTSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,10 +7,10 @@ import java.io.*; import java.util.*; -import org.basex.data.*; import org.basex.io.out.*; import org.basex.io.serial.*; import org.basex.query.*; +import org.basex.query.util.ft.*; import org.basex.query.value.item.*; import org.basex.util.*; import org.basex.util.list.*; @@ -53,7 +53,7 @@ } @Override - protected void startOpen(final byte[] name) { + protected void startOpen(final QNm name) { tb.reset(); } @@ -65,9 +65,9 @@ @Override protected void finishOpen() throws IOException { final byte[] attr = tb.toArray(); - String color = color(elem); + String color = color(elem.string()); if(color == null) color = attr.length == 0 ? ELEM1 : ELEM2; - print(concat(elem, attr), color); + print(concat(elem.string(), attr), color); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/HTMLSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/HTMLSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/HTMLSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/HTMLSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,6 +7,7 @@ import java.io.*; import org.basex.io.out.*; +import org.basex.query.value.item.*; import org.basex.util.hash.*; import org.basex.util.list.*; @@ -47,10 +48,13 @@ out.print(name); // don't append value for boolean attributes - final byte[] nm = concat(lc(elem), COLON, lc(name)); - if(BOOLEAN.contains(nm) && eq(name, value)) return; - // escape URI attributes - final byte[] val = escuri && URIS.contains(nm) ? escape(value) : value; + byte[] val = value; + if(!BOOLEAN.isEmpty() || !URIS.isEmpty()) { + final byte[] nm = concat(lc(elem.string()), ATT, lc(name)); + if(BOOLEAN.contains(nm) && eq(name, val)) return; + // escape URI attributes + if(escuri && URIS.contains(nm)) val = escape(val); + } out.print(ATT1); final int vl = val.length; @@ -61,7 +65,7 @@ } else if(ch == '"') { out.print(E_QUOT); } else if(ch == 0x9 || ch == 0xA) { - hex(ch); + printHex(ch); } else { encode(ch); } @@ -97,30 +101,31 @@ } @Override - protected void startOpen(final byte[] name) throws IOException { + protected void startOpen(final QNm name) throws IOException { doctype(null); if(sep) indent(); out.print(ELEM_O); - out.print(name); + out.print(name.string()); sep = indent; - script = SCRIPTS.contains(lc(name)); - if(content && eq(lc(elem), HEAD)) ct++; + script = SCRIPTS.contains(lc(name.local())); + if(content && eq(lc(elem.local()), HEAD)) ct++; } @Override protected void finishOpen() throws IOException { super.finishOpen(); - ct(false, true); + printCT(false, true); } @Override protected void finishEmpty() throws IOException { - if(ct(true, true)) return; + if(printCT(true, true)) return; out.print(ELEM_C); + final byte[] lc = lc(elem.local()); if(html5) { - if(EMPTIES5.contains(lc(elem))) return; + if(EMPTIES5.contains(lc)) return; } else { - if(EMPTIES.contains(lc(elem))) { + if(EMPTIES.contains(lc)) { final byte[] u = nsUri(EMPTY); if(u == null || u.length == 0) return; } @@ -132,21 +137,17 @@ @Override protected void finishClose() throws IOException { super.finishClose(); - script = script && !SCRIPTS.contains(lc(elem)); + script = script && !SCRIPTS.contains(lc(elem.local())); } @Override - boolean doctype(final byte[] type) throws IOException { - if(level != 0) return false; - if(!super.doctype(type) && html5) { - if(sep) indent(); - out.print(DOCTYPE); - if(type == null) out.print(HTML); - else out.print(type); - out.print(ELEM_C); - if(indent) newline(); + protected void doctype(final QNm type) throws IOException { + final boolean doc = docpub != null || docsys != null; + if(doc) { + printDoctype(type, docpub, docsys); + } else if(html5) { + printDoctype(type, null, null); } - return true; } // HTML Serializer: cache elements @@ -155,30 +156,30 @@ SCRIPTS.add("script"); SCRIPTS.add("style"); // boolean attributes - BOOLEAN.add("area:nohref"); - BOOLEAN.add("button:disabled"); - BOOLEAN.add("dir:compact"); - BOOLEAN.add("dl:compact"); - BOOLEAN.add("frame:noresize"); - BOOLEAN.add("hr:noshade"); - BOOLEAN.add("img:ismap"); - BOOLEAN.add("input:checked"); - BOOLEAN.add("input:disabled"); - BOOLEAN.add("input:readonly"); - BOOLEAN.add("menu:compact"); - BOOLEAN.add("object:declare"); - BOOLEAN.add("ol:compact"); - BOOLEAN.add("optgroup:disabled"); - BOOLEAN.add("option:selected"); - BOOLEAN.add("option:disabled"); - BOOLEAN.add("script:defer"); - BOOLEAN.add("select:multiple"); - BOOLEAN.add("select:disabled"); - BOOLEAN.add("td:nowrap"); - BOOLEAN.add("textarea:disabled"); - BOOLEAN.add("textarea:readonly"); - BOOLEAN.add("th:nowrap"); - BOOLEAN.add("ul:compact"); + BOOLEAN.add("area@nohref"); + BOOLEAN.add("button@disabled"); + BOOLEAN.add("dir@compact"); + BOOLEAN.add("dl@compact"); + BOOLEAN.add("frame@noresize"); + BOOLEAN.add("hr@noshade"); + BOOLEAN.add("img@ismap"); + BOOLEAN.add("input@checked"); + BOOLEAN.add("input@disabled"); + BOOLEAN.add("input@readonly"); + BOOLEAN.add("menu@compact"); + BOOLEAN.add("object@declare"); + BOOLEAN.add("ol@compact"); + BOOLEAN.add("optgroup@disabled"); + BOOLEAN.add("option@selected"); + BOOLEAN.add("option@disabled"); + BOOLEAN.add("script@defer"); + BOOLEAN.add("select@multiple"); + BOOLEAN.add("select@disabled"); + BOOLEAN.add("td@nowrap"); + BOOLEAN.add("textarea@disabled"); + BOOLEAN.add("textarea@readonly"); + BOOLEAN.add("th@nowrap"); + BOOLEAN.add("ul@compact"); // elements with an empty content model EMPTIES.add("area"); EMPTIES.add("base"); @@ -197,16 +198,13 @@ // elements with an empty content model EMPTIES5.add("area"); EMPTIES5.add("base"); - EMPTIES5.add("basefont"); EMPTIES5.add("br"); EMPTIES5.add("col"); EMPTIES5.add("command"); EMPTIES5.add("embed"); - EMPTIES5.add("frame"); EMPTIES5.add("hr"); EMPTIES5.add("img"); EMPTIES5.add("input"); - EMPTIES5.add("isindex"); EMPTIES5.add("keygen"); EMPTIES5.add("link"); EMPTIES5.add("meta"); @@ -215,42 +213,42 @@ EMPTIES5.add("track"); EMPTIES5.add("wbr"); // URI attributes - URIS.add("a:href"); - URIS.add("a:name"); - URIS.add("applet:codebase"); - URIS.add("area:href"); - URIS.add("base:href"); - URIS.add("blockquote:cite"); - URIS.add("body:background"); - URIS.add("button:datasrc"); - URIS.add("del:cite"); - URIS.add("div:datasrc"); - URIS.add("form:action"); - URIS.add("frame:longdesc"); - URIS.add("frame:src"); - URIS.add("head:profile"); - URIS.add("iframe:longdesc"); - URIS.add("iframe:src"); - URIS.add("img:longdesc"); - URIS.add("img:src"); - URIS.add("img:usemap"); - URIS.add("input:datasrc"); - URIS.add("input:src"); - URIS.add("input:usemap"); - URIS.add("ins:cite"); - URIS.add("link:href"); - URIS.add("object:archive"); - URIS.add("object:classid"); - URIS.add("object:codebase"); - URIS.add("object:data"); - URIS.add("object:datasrc"); - URIS.add("object:usemap"); - URIS.add("q:cite"); - URIS.add("script:for"); - URIS.add("script:src"); - URIS.add("select:datasrc"); - URIS.add("span:datasrc"); - URIS.add("table:datasrc"); - URIS.add("textarea:datasrc"); + URIS.add("a@href"); + URIS.add("a@name"); + URIS.add("applet@codebase"); + URIS.add("area@href"); + URIS.add("base@href"); + URIS.add("blockquote@cite"); + URIS.add("body@background"); + URIS.add("button@datasrc"); + URIS.add("del@cite"); + URIS.add("div@datasrc"); + URIS.add("form@action"); + URIS.add("frame@longdesc"); + URIS.add("frame@src"); + URIS.add("head@profile"); + URIS.add("iframe@longdesc"); + URIS.add("iframe@src"); + URIS.add("img@longdesc"); + URIS.add("img@src"); + URIS.add("img@usemap"); + URIS.add("input@datasrc"); + URIS.add("input@src"); + URIS.add("input@usemap"); + URIS.add("ins@cite"); + URIS.add("link@href"); + URIS.add("object@archive"); + URIS.add("object@classid"); + URIS.add("object@codebase"); + URIS.add("object@data"); + URIS.add("object@datasrc"); + URIS.add("object@usemap"); + URIS.add("q@cite"); + URIS.add("script@for"); + URIS.add("script@src"); + URIS.add("select@datasrc"); + URIS.add("span@datasrc"); + URIS.add("table@datasrc"); + URIS.add("textarea@datasrc"); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/json/JsonBasicSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/json/JsonBasicSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/json/JsonBasicSerializer.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/json/JsonBasicSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,247 @@ +package org.basex.io.serial.json; + +import static org.basex.io.parse.json.JsonConstants.*; +import static org.basex.query.QueryError.*; +import static org.basex.util.Token.*; + +import java.io.*; + +import org.basex.io.out.*; +import org.basex.io.parse.json.*; +import org.basex.io.serial.*; +import org.basex.query.*; +import org.basex.query.iter.*; +import org.basex.query.util.ft.*; +import org.basex.query.value.item.*; +import org.basex.query.value.node.*; +import org.basex.query.value.type.*; +import org.basex.util.*; +import org.basex.util.list.*; + +/** + * This class serializes items as JSON. The input must conform to the rules + * defined in the {@link JsonDirectConverter} and {@link JsonAttsConverter} class. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class JsonBasicSerializer extends JsonSerializer { + /** Output key. */ + private boolean printKey; + + /** + * Constructor. + * @param out print output + * @param opts serialization parameters + * @throws IOException I/O exception + */ + public JsonBasicSerializer(final PrintOutput out, final SerializerOptions opts) + throws IOException { + super(out, opts); + } + + @Override + protected void node(final ANode node) throws IOException { + if(level > 0) indent(); + + final BasicNodeIter iter = node.children(); + if(node.type == NodeType.DOC || node.type == NodeType.DEL) { + final ANode child = iter.next(); + if(child == null) throw error("Document has no child."); + if(iter.next() != null) throw error("Document has more than one child."); + node(child); + } else if(node.type == NodeType.ELM) { + final QNm name = node.qname(); + final byte[] type = name.local(); + if(!eq(name.uri(), QueryText.FN_URI)) + throw error("Element '%' has invalid namespace: '%'.", type, name.uri()); + + byte[] key = node.attribute(KEY); + if(printKey) { + if(key == null) throw error("Element '%' has no key.", type); + out.print('"'); + out.print(escape(key, node.attribute(ESCAPED_KEY))); + out.print("\":"); + } else { + if(key != null) throw error("Element '%' must have no key.", type); + } + + if(eq(type, NULL)) { + out.print(NULL); + if(iter.next() != null) throw error("Element '%' must have no children.", type); + } else if(eq(type, BOOLEAN)) { + byte[] value = value(iter, type); + if(value == null) throw error("Element '%' has no value.", type); + if(!eq(value, TRUE, FALSE)) throw error("Element '%' has invalid value: '%'.", type, value); + out.print(value); + } else if(eq(type, STRING)) { + byte[] value = value(iter, type); + out.print('"'); + if(value != null) out.print(escape(value, node.attribute(ESCAPED))); + out.print('"'); + } else if(eq(type, NUMBER)) { + byte[] value = value(iter, type); + if(value == null) throw error("Element '%' has no value.", type); + final Double d = toDouble(value); + if(d.isNaN() || d.isInfinite()) + throw error("Element '%' has invalid value: '%'.", type, value); + out.print(value); + } else if(eq(type, ARRAY)) { + out.print('['); + children(iter, false); + out.print(']'); + } else if(eq(type, MAP)) { + out.print('{'); + children(iter, true); + out.print('}'); + } else { + throw error("Invalid element: '%'", name); + } + } else { + throw error("Node must be an element."); + } + } + + @Override + protected void startOpen(final QNm name) throws IOException { + throw Util.notExpected(); + } + + @Override + protected void attribute(final byte[] name, final byte[] value, final boolean standalone) + throws IOException { + throw Util.notExpected(); + } + + @Override + protected void finishOpen() throws IOException { + throw Util.notExpected(); + } + + @Override + protected void text(final byte[] value, final FTPos ftp) throws IOException { + throw Util.notExpected(); + } + + @Override + protected void finishEmpty() throws IOException { + throw Util.notExpected(); + } + + @Override + protected void finishClose() throws IOException { + throw Util.notExpected(); + } + + @Override + protected void atomic(final Item value) throws IOException { + throw BXJS_SERIAL_X.getIO("Atomic values cannot be serialized"); + } + + /** + * Serializes child nodes. + * @param iter iterator + * @param pk print keys + * @throws IOException I/O exception + */ + private void children(final BasicNodeIter iter, final boolean pk) throws IOException { + boolean tmp = printKey; + printKey = pk; + level++; + boolean comma = false; + for(ANode child; (child = iter.next()) != null;) { + if(child.type == NodeType.ELM) { + if(comma) out.print(','); + node(child); + comma = true; + } else if(child.type == NodeType.TXT && !ws(child.string())) { + throw error("Element '%' must have no text nodes.", child.name()); + } + } + level--; + indent(); + printKey = tmp; + } + + /** + * Returns the value of a node. + * @param iter iterator + * @param type type + * @return value + * @throws QueryIOException query exception + */ + private byte[] value(final BasicNodeIter iter, final byte[] type) throws QueryIOException { + byte[] value = null; + for(ANode child; (child = iter.next()) != null;) { + if(child.type == NodeType.TXT) { + if(value != null) throw error("Element '%' has more than one child.", type); + value = child.string(); + } else if(child.type == NodeType.ELM) { + throw error("Element '%' must have no elements as child.", type); + } + } + return value; + } + + /** + * Returns a possibly escaped value. + * @param value value to escape + * @param flag escape flag + * @return escaped value + * @throws QueryIOException I/O exception + */ + private byte[] escape(final byte[] value, final byte[] flag) throws QueryIOException { + if(flag != null && !eq(flag, FALSE, TRUE)) + throw error("Value of escape attribute is invalid: '%'.", flag); + + final boolean check = flag != null && eq(flag, TRUE); + if(check) { + if(contains(value, '\\')) { + final TokenParser tp = new TokenParser(value); + while(tp.more()) { + int c = tp.next(); + if(c == '\\') { + if(!tp.more()) throw JSON_ESCAPE_X.getIO(value); + c = tp.next(); + if(indexOf(ESCAPES, c) == -1) throw JSON_ESCAPE_X.getIO(value); + if(c == 'u') { + for(int i = 0; i < 4; i++) { + if(!tp.more()) throw JSON_ESCAPE_X.getIO(value); + c = tp.next(); + if(c < '0' || c > '9' && c < 'A' || c > 'F' && c < 'a' || c > 'f') + throw JSON_ESCAPE_X.getIO(value); + } + } + } + } + } + } + + final ByteList bl = new ByteList(); + for(final byte c : value) { + if(c < 32 || c >= 128 && c <= 160) { + bl.add('\\'); + if(c == '\b') bl.add('b'); + else if(c == '\f') bl.add('f'); + else if(c == '\n') bl.add('n'); + else if(c == '\r') bl.add('r'); + else if(c == '\t') bl.add('t'); + else bl.add('u').add('0').add('0').add(HEX[c >> 4]).add(HEX[c & 0xF]); + } else { + if(c == '"' || !check && c == '\\') bl.add('\\'); + bl.add(c); + } + } + return bl.finish(); + } + + /** + * Raises an error with the specified message. + * @param msg error message + * @param ext error details + * @return I/O exception + */ + private static QueryIOException error(final String msg, final Object... ext) { + return JSON_INVALID_X.getIO(Util.inf(msg, ext)); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/json/JsonMLSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/json/JsonMLSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/json/JsonMLSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/json/JsonMLSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,13 +1,12 @@ package org.basex.io.serial.json; import static org.basex.query.QueryError.*; -import static org.basex.util.Token.*; import java.io.*; -import org.basex.data.*; import org.basex.io.out.*; import org.basex.io.serial.*; +import org.basex.query.util.ft.*; import org.basex.query.value.item.*; /** @@ -36,13 +35,13 @@ } @Override - protected void startOpen(final byte[] name) throws IOException { + protected void startOpen(final QNm name) throws IOException { if(level != 0) { out.print(','); indent(); } out.print("[\""); - for(final byte ch : local(name)) encode(ch); + for(final byte ch : name.local()) encode(ch); out.print('"'); att = false; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/json/JsonNodeSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/json/JsonNodeSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/json/JsonNodeSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/json/JsonNodeSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,12 +8,13 @@ import org.basex.build.json.*; import org.basex.build.json.JsonOptions.JsonFormat; -import org.basex.data.*; import org.basex.io.out.*; import org.basex.io.parse.json.*; import org.basex.io.serial.*; import org.basex.query.*; +import org.basex.query.util.ft.*; import org.basex.query.value.*; +import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.query.value.type.*; import org.basex.util.*; @@ -100,10 +101,10 @@ } @Override - protected void startOpen(final byte[] name) throws IOException { + protected void startOpen(final QNm name) throws IOException { types.set(level, null); comma.set(level + 1, false); - key = atts ? null : name; + key = atts ? null : name.string(); } @Override @@ -127,7 +128,7 @@ types.set(level, value); } else if(atts && eq(name, NAME)) { key = value; - if(!eq(elem, PAIR)) throw error("<%> found, expected", elem); + if(!eq(elem.string(), PAIR)) throw error("<%> found, expected", elem); } else if(!eq(name, XMLNS) && !startsWith(name, XMLNSC)) { throw error("<%> has invalid attribute \"%\"", elem, name); } @@ -142,7 +143,7 @@ indent(); final byte[] ptype = types.get(level - 1); if(eq(ptype, OBJECT)) { - if(atts && !eq(elem, PAIR)) throw error("<%> found, <%> expected", elem, PAIR); + if(atts && !eq(elem.string(), PAIR)) throw error("<%> found, <%> expected", elem, PAIR); if(key == null) throw error("<%> has no name attribute", elem); out.print('"'); final byte[] name = atts ? key : XMLToken.decode(key, lax); @@ -151,10 +152,10 @@ out.print("\":"); } else if(eq(ptype, ARRAY)) { if(atts) { - if(!eq(elem, ITEM)) throw error("<%> found, <%> expected", elem, ITEM); + if(!eq(elem.string(), ITEM)) throw error("<%> found, <%> expected", elem, ITEM); if(key != null) throw error("<%> must have no name attribute", elem); } else { - if(!eq(elem, VALUE)) throw error("<%> found, <%> expected", elem, VALUE); + if(!eq(elem.string(), VALUE)) throw error("<%> found, <%> expected", elem, VALUE); } } else { throw error("<%> is typed as \"%\" and cannot be nested", elems.get(level - 1), ptype); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/json/JsonSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/json/JsonSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/json/JsonSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/json/JsonSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,10 +4,10 @@ import static org.basex.util.Token.*; import java.io.*; -import java.util.*; import org.basex.build.json.*; import org.basex.io.out.*; +import org.basex.io.parse.json.*; import org.basex.io.serial.*; import org.basex.query.*; import org.basex.query.value.*; @@ -52,7 +52,7 @@ public void serialize(final Item item) throws IOException { if(sep) throw SERJSON.getIO(); if(item == null) { - out.print(NULL); + out.print(JsonConstants.NULL); } else { super.serialize(item); } @@ -114,11 +114,10 @@ out.print('['); boolean s = false; - final Iterator members = ((Array) item).members(); - while(members.hasNext()) { + for(final Value val : ((Array) item).members()) { if(s) out.print(','); indent(); - serialize(members.next()); + serialize(val); s = true; } @@ -198,7 +197,7 @@ @Override public void close() throws IOException { - if(!sep) out.print(NULL); + if(!sep) out.print(JsonConstants.NULL); super.close(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/MarkupSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/MarkupSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/MarkupSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/MarkupSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,14 +6,15 @@ import static org.basex.util.Token.*; import java.io.*; +import java.util.*; -import org.basex.data.*; import org.basex.io.out.*; import org.basex.query.*; +import org.basex.query.util.ft.*; +import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.util.*; import org.basex.util.ft.*; -import org.basex.util.hash.*; import org.basex.util.http.*; import org.basex.util.options.*; import org.basex.util.options.Options.YesNoOmit; @@ -28,7 +29,7 @@ /** System document type. */ String docsys; /** Public document type. */ - private String docpub; + String docpub; /** Flag for printing content type. */ int ct; @@ -46,11 +47,6 @@ /** Undeclare prefixes. */ final boolean undecl; - /** CData elements. */ - private final TokenSet cdata = new TokenSet(); - /** Suppress indentation elements. */ - private final TokenSet suppress = new TokenSet(); - /** Media type. */ private final String media; @@ -97,25 +93,10 @@ } } - final String supp = sopts.get(SUPPRESS_INDENTATION); - if(!supp.isEmpty()) { - for(final byte[] c : split(normalize(token(supp)), ' ')) { - if(c.length != 0) suppress.add(c); - } - } - - // collect CData elements final boolean html = this instanceof HTMLSerializer; final boolean xml = this instanceof XMLSerializer || this instanceof XHTMLSerializer; - if(xml || html) { - final String cdse = sopts.get(CDATA_SECTION_ELEMENTS); - if(!cdse.isEmpty()) { - for(final byte[] c : split(normalize(token(cdse)), ' ')) { - if(c.length == 0) continue; - if(!html || contains(c, ':') && (!html5 || !string(c).contains("html:"))) cdata.add(c); - } - } + if(xml || html) { if(undecl && ver.equals(V10)) throw SERUNDECL.getIO(); if(xml) { if(omitDecl) { @@ -160,7 +141,7 @@ if(cp == '"') { out.print(E_QUOT); } else if(cp == 0x9 || cp == 0xA) { - hex(cp); + printHex(cp); } else { encode(cp); } @@ -173,7 +154,8 @@ final byte[] val = norm(value); final int vl = val.length; if(ftp == null) { - if(cdata.isEmpty() || elems.isEmpty() || !cdata.contains(elems.peek())) { + final ArrayList qnames = cdata(); + if(qnames.isEmpty() || elems.isEmpty() || !qnames.contains(elems.peek())) { for(int k = 0; k < vl; k += cl(val, k)) encode(cp(val, k)); } else { out.print(CDATA_O); @@ -232,11 +214,11 @@ } @Override - protected void startOpen(final byte[] name) throws IOException { + protected void startOpen(final QNm name) throws IOException { doctype(name); if(sep) indent(); out.print(ELEM_O); - out.print(name); + out.print(name.string()); sep = true; } @@ -254,7 +236,7 @@ protected void finishClose() throws IOException { if(sep) indent(); out.print(ELEM_OS); - out.print(elem); + out.print(elem.string()); out.print(ELEM_C); sep = true; } @@ -271,7 +253,7 @@ } if(cp < ' ' && cp != '\n' && cp != '\t' || cp >= 0x7F && cp < 0xA0) { - hex(cp); + printHex(cp); } else if(cp == '&') { out.print(E_AMP); } else if(cp == '>') { @@ -288,21 +270,36 @@ /** * Prints the document type declaration. * @param type document type or {@code null} for html type - * @return true if doctype was added * @throws IOException I/O exception */ - boolean doctype(final byte[] type) throws IOException { - if(level != 0 || docsys == null && docpub == null) return false; + protected abstract void doctype(final QNm type) throws IOException; + + @Override + protected boolean ignore(final ANode node) { + return ct > 0 && eq(node.name(), META) && node.attribute(HTTPEQUIV) != null; + } + + /** + * Prints the document type declaration. + * @param type document type or {@code null} for html type + * @param pub doctype-public parameter + * @param sys doctype-system parameter + * @throws IOException I/O exception + */ + protected final void printDoctype(final QNm type, final String pub, final String sys) + throws IOException { + + if(level != 0) return; if(sep) indent(); out.print(DOCTYPE); - if(type == null) out.print(HTML); - else out.print(type); - if(docpub != null) out.print(' ' + PUBLIC + " \"" + docpub + '"'); - else out.print(' ' + SYSTEM); - if(docsys != null) out.print(" \"" + docsys + '"'); + out.print(type == null ? HTML : type.string()); + if(sys != null || pub != null) { + if(pub != null) out.print(' ' + PUBLIC + " \"" + pub + '"'); + else out.print(' ' + SYSTEM); + if(sys != null) out.print(" \"" + sys + '"'); + } out.print(ELEM_C); sep = true; - return true; } @Override @@ -310,8 +307,11 @@ if(atomic) { atomic = false; } else if(indent) { - if(!suppress.isEmpty() && !elems.isEmpty()) { - for(final byte[] t : elems) if(suppress.contains(t)) return; + final ArrayList qnames = suppress(); + if(!qnames.isEmpty()) { + for(final QNm e : elems) { + if(qnames.contains(e)) return; + } } super.indent(); } @@ -322,7 +322,7 @@ * @param cp codepoint (00-FF) * @throws IOException I/O exception */ - final void hex(final int cp) throws IOException { + protected final void printHex(final int cp) throws IOException { out.print("&#x"); if(cp > 0xF) out.print(HEX[cp >> 4]); out.print(HEX[cp & 0xF]); @@ -336,12 +336,12 @@ * @return {@code true} if declaration was printed * @throws IOException I/O exception */ - boolean ct(final boolean empty, final boolean html) throws IOException { + protected final boolean printCT(final boolean empty, final boolean html) throws IOException { if(ct != 1) return false; ct++; if(empty) finishOpen(); level++; - startOpen(META); + startOpen(new QNm(META)); attribute(HTTPEQUIV, CONTENT_TYPE, false); attribute(CONTENT, new TokenBuilder(media.isEmpty() ? MediaType.TEXT_HTML.toString() : media). add("; ").add(CHARSET).add('=').addExt(out.encoding()).finish(), false); @@ -356,11 +356,6 @@ return true; } - @Override - protected boolean ignore(final ANode node) { - return ct > 0 && eq(node.name(), META) && node.attribute(HTTPEQUIV) != null; - } - // PRIVATE METHODS ============================================================================== /** @@ -379,4 +374,65 @@ for(final String a : allowed) if(a.equals(val)) return val; throw SERNOTSUPP_X.getIO(Options.allowed(option, (Object[]) allowed)); } + + /** CData elements. */ + private ArrayList cdata; + + /** + * Initializes the CData elements. + * @return list + * @throws QueryIOException query I/O exception + */ + private ArrayList cdata() throws QueryIOException { + ArrayList list = cdata; + if(list == null) { + list = new ArrayList<>(); + final boolean html = this instanceof HTMLSerializer; + final String cdse = sopts.get(CDATA_SECTION_ELEMENTS); + for(final byte[] name : split(normalize(token(cdse)), ' ')) { + if(name.length == 0) continue; + final QNm qnm = resolve(name); + if(!html || contains(name, ':') && (!html5 || !string(name).contains("html:"))) { + list.add(qnm); + } + } + cdata = list; + } + return list; + } + + /** Suppress indentation elements. */ + private ArrayList suppress; + + /** + * Initializes and returns the elements whose contents must not be indented. + * @return list + * @throws QueryIOException query I/O exception + */ + private ArrayList suppress() throws QueryIOException { + ArrayList list = suppress; + if(list == null) { + list = new ArrayList<>(); + final String supp = sopts.get(SUPPRESS_INDENTATION); + for(final byte[] name : split(normalize(token(supp)), ' ')) { + if(name.length != 0) list.add(resolve(name)); + } + suppress = list; + } + return list; + } + + /** + * Resolves a QName. + * @param name name to be resolved + * @return list + * @throws QueryIOException query I/O exception + */ + private QNm resolve(final byte[] name) throws QueryIOException { + try { + return QNm.resolve(name, sc == null ? null : sc.elemNS, sc, null); + } catch(final QueryException ex) { + throw new QueryIOException(ex); + } + } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/RawSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/RawSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/RawSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/RawSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,9 +2,9 @@ import java.io.*; -import org.basex.data.*; import org.basex.io.out.*; import org.basex.query.*; +import org.basex.query.util.ft.*; import org.basex.query.value.item.*; /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/SAXSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/SAXSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/SAXSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/SAXSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,7 +4,7 @@ import java.io.*; -import org.basex.data.*; +import org.basex.query.util.ft.*; import org.basex.query.value.item.*; import org.basex.util.*; import org.xml.sax.*; @@ -140,7 +140,7 @@ private NSDecl namespaces; @Override - protected void startOpen(final byte[] name) { + protected void startOpen(final QNm name) { namespaces = new NSDecl(namespaces); attributes.clear(); } @@ -177,9 +177,9 @@ attrs.addAttribute(uri, lname, rname, null, value); } - final String uri = string(namespaces.get(prefix(elem))); - final String lname = string(local(elem)); - final String rname = string(elem); + final String uri = string(namespaces.get(elem.prefix())); + final String lname = string(elem.local()); + final String rname = string(elem.string()); contentHandler.startElement(uri, lname, rname, attrs); } catch(final SAXException ex) { @@ -196,7 +196,7 @@ @Override protected void finishClose() throws IOException { try { - final String name = string(elem); + final String name = string(elem.string()); contentHandler.endElement("", name, name); namespaces = namespaces.getParent(); } catch(final SAXException ex) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/Serializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/Serializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/Serializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/Serializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,6 +6,7 @@ import java.io.*; import java.nio.charset.*; +import java.util.*; import org.basex.build.csv.*; import org.basex.build.csv.CsvOptions.CsvFormat; @@ -17,6 +18,7 @@ import org.basex.io.serial.json.*; import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.util.ft.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.query.value.type.*; @@ -32,11 +34,11 @@ */ public abstract class Serializer implements Closeable { /** Stack with names of opened elements. */ - protected final TokenList elems = new TokenList(); + protected final Stack elems = new Stack<>(); /** Current level. */ protected int level; /** Current element name. */ - protected byte[] elem; + protected QNm elem; /** Indentation flag. */ protected boolean indent; @@ -45,6 +47,8 @@ /** Stack with namespace size pointers. */ private final IntList nstack = new IntList(); + /** Static context. */ + protected StaticContext sc; /** Indicates if more than one item was serialized. */ protected boolean more; /** Indicates if an element is currently being opened. */ @@ -99,9 +103,10 @@ : new CsvDirectSerializer(po, so); case JSON: final JsonSerialOptions jopts = so.get(SerializerOptions.JSON); - return jopts.get(JsonOptions.FORMAT) == JsonFormat.JSONML - ? new JsonMLSerializer(po, so) - : new JsonNodeSerializer(po, so); + final JsonFormat jformat = jopts.get(JsonOptions.FORMAT); + return jformat == JsonFormat.JSONML ? new JsonMLSerializer(po, so) : + jformat == JsonFormat.BASIC ? new JsonBasicSerializer(po, so) : + new JsonNodeSerializer(po, so); case XML: return new XMLSerializer(po, so); default: @@ -147,6 +152,16 @@ */ public void reset() { } + /** + * Assigns the static context. + * @param sctx static context + * @return serializer + */ + public Serializer sc(final StaticContext sctx) { + sc = sctx; + return this; + } + // PROTECTED METHODS ================================================================== /** @@ -178,7 +193,7 @@ } else { // serialize elements (code will never be called for attributes) final QNm name = node.qname(); - openElement(name.string()); + openElement(name); // serialize declared namespaces final Atts nsp = node.namespaces(); @@ -188,8 +203,8 @@ // serialize attributes final boolean i = indent; - AxisMoreIter ai = node.attributes(); - for(ANode nd; (nd = ai.next()) != null;) { + BasicNodeIter iter = node.attributes(); + for(ANode nd; (nd = iter.next()) != null;) { final byte[] n = nd.name(); final byte[] v = nd.string(); attribute(n, v, false); @@ -197,8 +212,8 @@ } // serialize children - ai = node.children(); - for(ANode n; (n = ai.next()) != null;) node(n); + iter = node.children(); + for(ANode n; (n = iter.next()) != null;) node(n); closeElement(); indent = i; } @@ -210,7 +225,7 @@ * @param name element name * @throws IOException I/O exception */ - protected final void openElement(final byte[] name) throws IOException { + protected final void openElement(final QNm name) throws IOException { prepare(); opening = true; elem = name; @@ -228,9 +243,10 @@ finishEmpty(); opening = false; } else { - elem = elems.pop(); + elem = elems.peek(); level--; finishClose(); + elems.pop(); } } @@ -305,7 +321,7 @@ * @throws IOException I/O exception */ @SuppressWarnings("unused") - protected void startOpen(final byte[] name) throws IOException { } + protected void startOpen(final QNm name) throws IOException { } /** * Finishes an opening element node. @@ -379,21 +395,21 @@ */ private void node(final DBNode node) throws IOException { final FTPosData ft = node instanceof FTPosNode ? ((FTPosNode) node).ftpos : null; - final Data data = node.data; - int p = node.pre; - int k = data.kind(p); - if(k == Data.ATTR) throw SERATTR_X.getIO(node); + final Data data = node.data(); + int pre = node.pre(); + int kind = data.kind(pre); + if(kind == Data.ATTR) throw SERATTR_X.getIO(node); boolean doc = false; - final TokenSet nsp = data.nspaces.size() == 0 ? null : new TokenSet(); + final TokenSet nsp = data.nspaces.isEmpty() ? null : new TokenSet(); final IntList pars = new IntList(); final BoolList indt = new BoolList(); // loop through all table entries - final int s = p + data.size(p, k); - while(p < s && !finished()) { - k = data.kind(p); - final int r = data.parent(p, k); + final int s = pre + data.size(pre, kind); + while(pre < s && !finished()) { + kind = data.kind(pre); + final int r = data.parent(pre, kind); // close opened elements... while(!pars.isEmpty() && pars.peek() >= r) { @@ -402,35 +418,35 @@ pars.pop(); } - if(k == Data.DOC) { + if(kind == Data.DOC) { if(doc) closeDoc(); - openDoc(data.text(p++, true)); + openDoc(data.text(pre++, true)); doc = true; - } else if(k == Data.TEXT) { - prepareText(data.text(p, true), ft != null ? ft.get(data, p) : null); - p++; - } else if(k == Data.COMM) { - prepareComment(data.text(p++, true)); + } else if(kind == Data.TEXT) { + prepareText(data.text(pre, true), ft != null ? ft.get(data, pre) : null); + pre++; + } else if(kind == Data.COMM) { + prepareComment(data.text(pre++, true)); } else { - if(k == Data.PI) { - preparePi(data.name(p, Data.PI), data.atom(p++)); + if(kind == Data.PI) { + preparePi(data.name(pre, Data.PI), data.atom(pre++)); } else { // add element node - final byte[] name = data.name(p, k); - openElement(name); + final byte[] name = data.name(pre, kind); + final byte[] uri = data.nspaces.uri(data.uriId(pre, kind)); + openElement(new QNm(name, uri)); // add namespace definitions if(nsp != null) { // add namespaces from database nsp.clear(); - int pp = p; + int pp = pre; // check namespace of current element - final byte[] u = data.nspaces.uri(data.uri(p, k)); - namespace(prefix(name), u == null ? EMPTY : u, false); + namespace(prefix(name), uri == null ? EMPTY : uri, false); do { - final Atts ns = data.ns(pp); + final Atts ns = data.namespaces(pp); final int nl = ns.size(); for(int n = 0; n < nl; n++) { final byte[] pref = ns.name(n); @@ -445,10 +461,10 @@ // serialize attributes indt.push(indent); - final int as = p + data.attSize(p, k); - while(++p != as) { - final byte[] n = data.name(p, Data.ATTR); - final byte[] v = data.text(p, false); + final int as = pre + data.attSize(pre, kind); + while(++pre != as) { + final byte[] n = data.name(pre, Data.ATTR); + final byte[] v = data.text(pre, false); attribute(n, v, false); if(eq(n, XML_SPACE) && indent) indent = !eq(v, PRESERVE); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/TextSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/TextSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/TextSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/TextSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,8 +2,8 @@ import java.io.*; -import org.basex.data.*; import org.basex.io.out.*; +import org.basex.query.util.ft.*; /** * This class serializes items as text. diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/XHTMLSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/XHTMLSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/XHTMLSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/XHTMLSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,6 +6,7 @@ import java.io.*; import org.basex.io.out.*; +import org.basex.query.value.item.*; /** * This class serializes items as XHTML. @@ -29,27 +30,30 @@ throws IOException { // escape URI attributes - final byte[] nm = concat(lc(elem), COLON, lc(name)); + final byte[] nm = concat(lc(elem.local()), COLON, lc(name)); final byte[] val = escuri && HTMLSerializer.URIS.contains(nm) ? escape(value) : value; super.attribute(name, val, standalone); } @Override - protected void startOpen(final byte[] value) throws IOException { + protected void startOpen(final QNm value) throws IOException { super.startOpen(value); - if(content && eq(lc(elem), HEAD)) ct++; + if(content && eq(lc(elem.local()), HEAD)) ct++; } @Override protected void finishOpen() throws IOException { super.finishOpen(); - ct(false, false); + printCT(false, false); } @Override protected void finishEmpty() throws IOException { - if(ct(true, false)) return; - if((html5 ? HTMLSerializer.EMPTIES5 : HTMLSerializer.EMPTIES).contains(lc(elem))) { + if(printCT(true, false)) return; + final byte[] lc = lc(elem.local()); + if(html5 && HTMLSerializer.EMPTIES5.contains(lc)) { + out.print(ELEM_SC); + } else if(!html5 && HTMLSerializer.EMPTIES.contains(lc) && eq(elem.uri(), XHTML_URI)) { out.print(' '); out.print(ELEM_SC); } else { @@ -60,16 +64,11 @@ } @Override - protected boolean doctype(final byte[] type) throws IOException { - if(level != 0) return false; - if(!super.doctype(type) && html5) { - if(sep) indent(); - out.print(DOCTYPE); - if(type == null) out.print(HTML); - else out.print(type); - out.print(ELEM_C); - newline(); + protected void doctype(final QNm type) throws IOException { + if(html5 && docsys == null) { + printDoctype(type, null, null); + } else if(docsys != null) { + printDoctype(type, docpub, docsys); } - return true; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/XMLSerializer.java basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/XMLSerializer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/io/serial/XMLSerializer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/io/serial/XMLSerializer.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,9 +5,9 @@ import java.io.*; -import org.basex.data.*; import org.basex.io.out.*; import org.basex.query.*; +import org.basex.query.util.ft.*; import org.basex.query.value.item.*; /** @@ -31,7 +31,7 @@ } @Override - protected void startOpen(final byte[] name) throws IOException { + protected void startOpen(final QNm name) throws IOException { if(elems.isEmpty()) { if(root) check(); root = true; @@ -51,6 +51,11 @@ super.atomic(it); } + @Override + protected void doctype(final QNm type) throws IOException { + if(docsys != null) printDoctype(type, docpub, docsys); + } + /** * Checks if document serialization is valid. * @throws QueryIOException query I/O exception diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/BaseXPragma.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/BaseXPragma.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/BaseXPragma.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/BaseXPragma.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,41 @@ +package org.basex.query.expr; + +import org.basex.query.*; +import org.basex.query.expr.Expr.Flag; +import org.basex.query.value.item.*; +import org.basex.util.*; + +/** + * Pragma for database options. + * + * @author BaseX Team 2005-15, BSD License + * @author Leo Woerteler + */ +public final class BaseXPragma extends Pragma { + /** + * Constructor. + * @param name name of pragma + * @param value optional value + */ + public BaseXPragma(final QNm name, final byte[] value) { + super(name, value); + } + + @Override + void init(final QueryContext qc, final InputInfo info) throws QueryException { + } + + @Override + void finish(final QueryContext qc) { + } + + @Override + public boolean has(final Flag flag) { + return flag == Flag.NDT && Token.eq(name.local(), Token.token(QueryText.NON_DETERMNISTIC)); + } + + @Override + public Pragma copy() { + return new BaseXPragma(name, value); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/CachedFilter.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/CachedFilter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/CachedFilter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/CachedFilter.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,6 +2,7 @@ import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -28,6 +29,11 @@ @Override public Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); + } + + @Override + public Value value(final QueryContext qc) throws QueryException { Value val = root.value(qc); final Value cv = qc.value; final long cs = qc.size; @@ -35,7 +41,7 @@ try { // evaluate first predicate, based on incoming value - final ValueBuilder vb = new ValueBuilder(); + final ItemList buffer = new ItemList(); Expr pred = preds[0]; long vs = val.size(); qc.size = vs; @@ -48,7 +54,7 @@ final Item test = pred.test(qc, info); if(test != null) { if(scoring) item.score(test.score()); - vb.add(item); + buffer.add(item); } qc.pos++; } @@ -58,26 +64,26 @@ // evaluate remaining predicates, based on value builder final int pl = preds.length; for(int i = 1; i < pl; i++) { - vs = vb.size(); + vs = buffer.size(); pred = preds[i]; qc.size = vs; qc.pos = 1; int c = 0; for(int s = 0; s < vs; ++s) { - final Item item = vb.get(s); + final Item item = buffer.get(s); qc.value = item; final Item test = pred.test(qc, info); if(test != null) { if(scoring) item.score(test.score()); - vb.set(c++, item); + buffer.set(c++, item); } qc.pos++; } - vb.size(c); + buffer.size(c); } // return resulting values - return vb; + return buffer.value(); } finally { qc.value = cv; qc.size = cs; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/CachedMap.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/CachedMap.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/CachedMap.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/CachedMap.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,6 +2,7 @@ import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.var.*; @@ -26,15 +27,20 @@ @Override public Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); + } + + @Override + public Value value(final QueryContext qc) throws QueryException { final Value cv = qc.value; final long cp = qc.pos, cs = qc.size; try { - ValueBuilder result = new ValueBuilder().add(qc.value(exprs[0])); + ItemList result = qc.value(exprs[0]).cache(); final int el = exprs.length; for(int e = 1; e < el; e++) { qc.pos = 0; qc.size = result.size(); - final ValueBuilder vb = new ValueBuilder((int) result.size()); + final ItemList vb = new ItemList(result.size()); for(final Item it : result) { qc.pos++; qc.value = it; @@ -42,7 +48,7 @@ } result = vb; } - return result; + return result.value(); } finally { qc.value = cv; qc.size = cs; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/CmpR.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/CmpR.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/CmpR.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/CmpR.java 2015-07-14 10:54:40.000000000 +0000 @@ -71,13 +71,15 @@ * @return new or original expression */ static ParseExpr get(final CmpG cmp) { - final Expr e = cmp.exprs[1]; - if(e instanceof RangeSeq) { - final RangeSeq rs = (RangeSeq) e; + final Expr e1 = cmp.exprs[0], e2 = cmp.exprs[1]; + if(e1.has(Flag.NDT) || e1.has(Flag.UPD)) return cmp; + + if(e2 instanceof RangeSeq) { + final RangeSeq rs = (RangeSeq) e2; return get(cmp, rs.start(), rs.end()); } - if(e instanceof ANum) { - final double d = ((ANum) cmp.exprs[1]).dbl(); + if(e2 instanceof ANum) { + final double d = ((ANum) e2).dbl(); return get(cmp, d, d); } return cmp; @@ -189,7 +191,7 @@ private Stats key(final IndexInfo ii, final boolean text) { // statistics are not up-to-date final Data data = ii.ic.data; - if(!data.meta.uptodate || data.nspaces.size() != 0 || !(expr instanceof AxisPath)) return null; + if(!data.meta.uptodate || !data.nspaces.isEmpty() || !(expr instanceof AxisPath)) return null; NameTest test = ii.test; if(test == null) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/CmpSR.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/CmpSR.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/CmpSR.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/CmpSR.java 2015-07-14 10:54:40.000000000 +0000 @@ -73,14 +73,15 @@ * @throws QueryException query exception */ static ParseExpr get(final CmpG cmp) throws QueryException { - if(!(cmp.exprs[1] instanceof AStr)) return cmp; - final byte[] d = ((Item) cmp.exprs[1]).string(cmp.info); - final Expr e = cmp.exprs[0]; + final Expr e1 = cmp.exprs[0], e2 = cmp.exprs[1]; + if(e1.has(Flag.NDT) || e1.has(Flag.UPD) || !(e2 instanceof AStr)) return cmp; + + final byte[] d = ((AStr) e2).string(cmp.info); switch(cmp.op.op) { - case GE: return new CmpSR(e, d, true, null, true, cmp.coll, cmp.info); - case GT: return new CmpSR(e, d, false, null, true, cmp.coll, cmp.info); - case LE: return new CmpSR(e, null, true, d, true, cmp.coll, cmp.info); - case LT: return new CmpSR(e, null, true, d, false, cmp.coll, cmp.info); + case GE: return new CmpSR(e1, d, true, null, true, cmp.coll, cmp.info); + case GT: return new CmpSR(e1, d, false, null, true, cmp.coll, cmp.info); + case LE: return new CmpSR(e1, null, true, d, true, cmp.coll, cmp.info); + case LT: return new CmpSR(e1, null, true, d, false, cmp.coll, cmp.info); default: return cmp; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/constr/Constr.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/constr/Constr.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/constr/Constr.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/constr/Constr.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,8 +2,6 @@ import static org.basex.query.QueryError.*; -import java.util.*; - import org.basex.query.*; import org.basex.query.expr.*; import org.basex.query.iter.*; @@ -88,9 +86,8 @@ */ private boolean add(final QueryContext qc, final Item it) throws QueryException { if(it instanceof Array) { - final Iterator members = ((Array) it).members(); - while(members.hasNext()) { - for(final Item i : members.next()) { + for(final Value val : ((Array) it).members()) { + for(final Item i : val) { if(!add(qc, i)) return false; } } @@ -153,8 +150,8 @@ } else if(ip == NodeType.DOC) { // type: document node - final AxisIter ai = node.children(); - for(ANode ch; (ch = ai.next()) != null && add(qc, ch);); + final BasicNodeIter iter = node.children(); + for(ANode ch; (ch = iter.next()) != null && add(qc, ch);); } else { // type: element/comment/processing instruction node diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Context.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Context.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Context.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Context.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,85 +0,0 @@ -package org.basex.query.expr; - -import org.basex.core.locks.*; -import org.basex.query.*; -import org.basex.query.iter.*; -import org.basex.query.util.*; -import org.basex.query.value.*; -import org.basex.query.value.item.*; -import org.basex.query.value.type.*; -import org.basex.query.var.*; -import org.basex.util.*; -import org.basex.util.hash.*; - -/** - * Context value. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -public final class Context extends Simple { - /** - * Constructor. - * @param info input info - */ - public Context(final InputInfo info) { - super(info); - seqType = SeqType.ITEM_ZM; - } - - @Override - public Context compile(final QueryContext qc, final VarScope scp) { - return optimize(qc, scp); - } - - @Override - public Context optimize(final QueryContext qc, final VarScope scp) { - if(qc.value != null) seqType = qc.value.seqType(); - return this; - } - - @Override - public Iter iter(final QueryContext qc) throws QueryException { - return ctxValue(qc).iter(); - } - - @Override - public Value value(final QueryContext qc) throws QueryException { - return ctxValue(qc); - } - - @Override - public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - return ctxValue(qc).item(qc, info); - } - - @Override - public boolean has(final Flag flag) { - return flag == Flag.CTX; - } - - @Override - public boolean removable(final Var var) { - return false; - } - - @Override - public Expr copy(final QueryContext qc, final VarScope scp, final IntObjMap vs) { - return copyType(new Context(info)); - } - - @Override - public boolean accept(final ASTVisitor visitor) { - return visitor.lock(DBLocking.CONTEXT) && super.accept(visitor); - } - - @Override - public boolean sameAs(final Expr cmp) { - return cmp instanceof Context; - } - - @Override - public String toString() { - return "."; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ContextValue.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ContextValue.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ContextValue.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ContextValue.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,85 @@ +package org.basex.query.expr; + +import org.basex.core.locks.*; +import org.basex.query.*; +import org.basex.query.iter.*; +import org.basex.query.util.*; +import org.basex.query.value.*; +import org.basex.query.value.item.*; +import org.basex.query.value.type.*; +import org.basex.query.var.*; +import org.basex.util.*; +import org.basex.util.hash.*; + +/** + * Context value. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class ContextValue extends Simple { + /** + * Constructor. + * @param info input info + */ + public ContextValue(final InputInfo info) { + super(info); + seqType = SeqType.ITEM_ZM; + } + + @Override + public ContextValue compile(final QueryContext qc, final VarScope scp) { + return optimize(qc, scp); + } + + @Override + public ContextValue optimize(final QueryContext qc, final VarScope scp) { + if(qc.value != null) seqType = qc.value.seqType(); + return this; + } + + @Override + public Iter iter(final QueryContext qc) throws QueryException { + return ctxValue(qc).iter(); + } + + @Override + public Value value(final QueryContext qc) throws QueryException { + return ctxValue(qc); + } + + @Override + public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { + return ctxValue(qc).item(qc, info); + } + + @Override + public boolean has(final Flag flag) { + return flag == Flag.CTX; + } + + @Override + public boolean removable(final Var var) { + return false; + } + + @Override + public Expr copy(final QueryContext qc, final VarScope scp, final IntObjMap vs) { + return copyType(new ContextValue(info)); + } + + @Override + public boolean accept(final ASTVisitor visitor) { + return visitor.lock(DBLocking.CONTEXT) && super.accept(visitor); + } + + @Override + public boolean sameAs(final Expr cmp) { + return cmp instanceof ContextValue; + } + + @Override + public String toString() { + return "."; + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/DBPragma.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/DBPragma.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/DBPragma.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/DBPragma.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,6 +5,7 @@ import org.basex.core.*; import org.basex.query.*; +import org.basex.query.expr.Expr.*; import org.basex.query.value.item.*; import org.basex.util.*; import org.basex.util.options.*; @@ -23,13 +24,13 @@ /** * Constructor. - * @param n name of pragma - * @param o option - * @param v optional value + * @param name name of pragma + * @param option option + * @param value optional value */ - public DBPragma(final QNm n, final Option o, final byte[] v) { - super(n, v); - option = o; + public DBPragma(final QNm name, final Option option, final byte[] value) { + super(name, value); + this.option = option; } @Override @@ -48,6 +49,11 @@ } @Override + public boolean has(final Flag flag) { + return false; + } + + @Override public Pragma copy() { return new DBPragma(name, option, value); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Except.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Except.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Except.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Except.java 2015-07-14 10:54:40.000000000 +0000 @@ -56,21 +56,21 @@ } @Override - protected NodeSeqBuilder eval(final Iter[] iter) throws QueryException { - final NodeSeqBuilder nc = new NodeSeqBuilder().check(); + protected ANodeList eval(final Iter[] iter) throws QueryException { + final ANodeList list = new ANodeList().check(); - for(Item it; (it = iter[0].next()) != null;) nc.add(toNode(it)); - final boolean db = nc.dbnodes(); + for(Item it; (it = iter[0].next()) != null;) list.add(toNode(it)); + final boolean db = list.dbnodes(); final int el = exprs.length; - for(int e = 1; e < el && nc.size() != 0; e++) { + for(int e = 1; e < el && !list.isEmpty(); e++) { final Iter ir = iter[e]; for(Item it; (it = ir.next()) != null;) { - final int i = nc.indexOf(toNode(it), db); - if(i != -1) nc.delete(i); + final int i = list.indexOf(toNode(it), db); + if(i != -1) list.delete(i); } } - return nc; + return list; } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Expr.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Expr.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Expr.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Expr.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,5 @@ package org.basex.query.expr; -import static org.basex.query.QueryText.*; - import java.util.*; import org.basex.data.*; @@ -34,7 +32,7 @@ /** Creates new fragments. Example: node constructor. */ CNS, /** Depends on context. Example: context node. */ CTX, /** Non-deterministic. Example: random:double(). */ NDT, - /** Focus-dependent. Example: position(). */ FCS, + /** Positional access. Example: position(). */ POS, /** Performs updates. Example: insert expression. */ UPD, /** Invokes user-supplied functions. Example: fold. */ HOF, } @@ -180,8 +178,8 @@ } /** - * Returns the data reference bound to this expression. This method is overwritten - * by the values {@link DBNode} and {@link DBNodeSeq} and some more expressions. + * Returns the data reference bound to this expression. This method is currently overwritten + * by {@link DBNode}, {@link DBNodeSeq}, {@link Path} and {@link VarRef}. * @return data reference */ public Data data() { @@ -198,7 +196,7 @@ * Indicates if an expression has the specified compiler property. This method must only be * called at compile time. It is invoked to test properties of sub-expressions. * It returns {@code true} if at least one test is successful. - * @param flag flag to be found + * @param flag flag to be checked * @return result of check */ public abstract boolean has(final Flag flag); @@ -310,7 +308,7 @@ // return true if a deterministic expression returns at least one node final SeqType st = seqType(); if(st.type instanceof NodeType && st.occ.min >= 1 && !has(Flag.UPD) && !has(Flag.NDT)) { - qc.compInfo(OPTWRITE, this); + qc.compInfo(QueryText.OPTWRITE, this); return Bln.TRUE; } return this; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Extension.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Extension.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Extension.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Extension.java 2015-07-14 10:54:40.000000000 +0000 @@ -83,6 +83,12 @@ } @Override + public boolean has(final Flag flag) { + for(final Pragma p : pragmas) if(p.has(flag)) return true; + return super.has(flag); + } + + @Override public String toString() { final StringBuilder sb = new StringBuilder(); for(final Pragma p : pragmas) sb.append(p).append(' '); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Filter.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Filter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Filter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Filter.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,10 +6,10 @@ import org.basex.query.expr.gflwor.*; import org.basex.query.expr.path.*; import org.basex.query.func.*; +import org.basex.query.func.fn.*; import org.basex.query.util.*; import org.basex.query.value.*; import org.basex.query.value.item.*; -import org.basex.query.value.seq.*; import org.basex.query.var.*; import org.basex.util.*; @@ -55,11 +55,11 @@ private static Expr path(final Expr root, final Expr... preds) { // no predicates: return root if(preds.length == 0) return root; - // axis path + // axis path: attach predicates to last step if(root instanceof AxisPath) { - // predicate must not be numeric + // predicates must not be numeric: (//x)[1] != //x[1] for(final Expr pred : preds) { - if(pred.seqType().mayBeNumber() || pred.has(Flag.FCS)) return null; + if(pred.seqType().mayBeNumber() || pred.has(Flag.POS)) return null; } return ((AxisPath) root).addPreds(preds); } @@ -131,14 +131,14 @@ if(ex != null) return ex.optimize(qc, scp); // try to rewrite filter to index access - if(root instanceof Context || root instanceof Value && root.data() != null) { + if(root instanceof ContextValue || root instanceof Value && root.data() != null) { final Path ip = Path.get(info, root, Step.get(info, SELF, Test.NOD, preds)); final Expr ie = ip.index(qc, Path.initial(qc, root)); if(ie != ip) return ie; } // no numeric predicates.. use simple iterator - if(!super.has(Flag.FCS)) return copyType(new IterFilter(info, root, preds)); + if(!super.has(Flag.POS)) return copyType(new IterFilter(info, root, preds)); // evaluate positional predicates Expr e = root; @@ -152,7 +152,7 @@ if(last) { if(e.isValue()) { // return sub-sequence - e = SubSeq.get((Value) e, e.size() - 1, 1); + e = FnSubsequence.eval((Value) e, e.size(), 1); } else { // rewrite positional predicate to basex:last-from e = Function._BASEX_LAST_FROM.get(null, info, e); @@ -161,7 +161,7 @@ } else if(pos != null) { if(e.isValue()) { // return sub-sequence - e = SubSeq.get((Value) e, pos.min - 1, pos.max - pos.min + 1); + e = FnSubsequence.eval((Value) e, pos.min, pos.max - pos.min + 1); } else if(pos.min == pos.max) { // example: expr[pos] -> basex:item-at(expr, pos.min) e = Function._BASEX_ITEM_AT.get(null, info, e, Int.get(pos.min)); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTAnd.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTAnd.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTAnd.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTAnd.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,11 +2,11 @@ import static org.basex.query.QueryText.*; -import org.basex.data.*; import org.basex.query.*; import org.basex.query.expr.*; import org.basex.query.iter.*; import org.basex.query.util.*; +import org.basex.query.util.ft.*; import org.basex.query.value.node.*; import org.basex.query.var.*; import org.basex.util.*; @@ -77,7 +77,7 @@ return null; } - final int d = it[0].pre - it[i].pre; + final int d = it[0].pre() - it[i].pre(); if(negated[i]) { if(d >= 0) { if(d == 0) it[0] = ir[0].next(); @@ -113,14 +113,14 @@ * @param i2 second item */ private static void and(final FTNode i1, final FTNode i2) { - final FTMatches all = new FTMatches((byte) Math.max(i1.all.pos, i2.all.pos)); - for(final FTMatch s1 : i1.all) { - for(final FTMatch s2 : i2.all) { + final FTMatches all = new FTMatches((byte) Math.max(i1.matches().pos, i2.matches().pos)); + for(final FTMatch s1 : i1.matches()) { + for(final FTMatch s2 : i2.matches()) { all.add(new FTMatch(s1.size() + s2.size()).add(s1).add(s2)); } } i1.score(Scoring.avg(i1.score() + i2.score(), 2)); - i1.all = all; + i1.matches(all); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTContains.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTContains.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTContains.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTContains.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,11 +2,11 @@ import static org.basex.query.QueryText.*; -import org.basex.data.*; import org.basex.query.*; import org.basex.query.expr.*; import org.basex.query.iter.*; import org.basex.query.util.*; +import org.basex.query.util.ft.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.query.value.type.*; @@ -54,13 +54,14 @@ for(Item it; (it = iter.next()) != null;) { lex.init(it.string(info)); final FTNode item = ftexpr.item(qc, info); - if(item.all.matches()) { + final FTMatches all = item.matches(); + if(all.matches()) { f = true; if(scoring) s += item.score(); // cache entry for visualizations or ft:mark/ft:extract if(ftPosData != null && it instanceof DBNode) { final DBNode node = (DBNode) it; - ftPosData.add(node.data, node.pre, item.all); + ftPosData.add(node.data(), node.pre(), all); } } c++; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTContent.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTContent.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTContent.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTContent.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,8 +2,8 @@ import static org.basex.query.QueryText.*; -import org.basex.data.*; import org.basex.query.*; +import org.basex.query.util.ft.*; import org.basex.query.value.node.*; import org.basex.query.var.*; import org.basex.util.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTDistance.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTDistance.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTDistance.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTDistance.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,10 +2,10 @@ import static org.basex.query.QueryText.*; -import org.basex.data.*; import org.basex.query.*; import org.basex.query.expr.*; import org.basex.query.util.*; +import org.basex.query.util.ft.*; import org.basex.query.value.node.*; import org.basex.query.var.*; import org.basex.util.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTFilter.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTFilter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTFilter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTFilter.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,9 +1,9 @@ package org.basex.query.expr.ft; -import org.basex.data.*; import org.basex.query.*; import org.basex.query.iter.*; import org.basex.query.util.*; +import org.basex.query.util.ft.*; import org.basex.query.value.node.*; import org.basex.util.*; import org.basex.util.ft.*; @@ -71,7 +71,7 @@ private boolean filter(final QueryContext qc, final FTNode item, final FTLexer lex) throws QueryException { - final FTMatches all = item.all; + final FTMatches all = item.matches(); for(int a = 0; a < all.size(); a++) { if(!filter(qc, all.match[a], lex)) all.delete(a--); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTIndexAccess.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTIndexAccess.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTIndexAccess.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTIndexAccess.java 2015-07-14 10:54:40.000000000 +0000 @@ -50,9 +50,9 @@ // assign scoring if(qc.scoring) it.score(); // cache entry for visualizations or ft:mark/ft:extract - if(qc.ftPosData != null) qc.ftPosData.add(it.data, it.pre, it.all); + if(qc.ftPosData != null) qc.ftPosData.add(it.data(), it.pre(), it.matches()); // remove matches reference to save memory - it.all = null; + it.matches(null); } return it; } @@ -110,7 +110,7 @@ Expr e = ftexpr; if(ftexpr instanceof FTWords) { final FTWords f = (FTWords) ftexpr; - if(f.mode == FTMode.ANY && f.occ == null) e = f.query; + if(f.mode == FTMode.ANY && f.occ == null && f.ftt == null) e = f.query; } return Function._FT_SEARCH.get(null, info, Str.get(ictx.data.meta.name), e).toString(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTMildNot.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTMildNot.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTMildNot.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTMildNot.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,10 +3,10 @@ import static org.basex.query.QueryError.*; import static org.basex.query.QueryText.*; -import org.basex.data.*; import org.basex.query.*; import org.basex.query.iter.*; import org.basex.query.util.*; +import org.basex.query.util.ft.*; import org.basex.query.value.node.*; import org.basex.query.var.*; import org.basex.util.*; @@ -48,13 +48,13 @@ @Override public FTNode next() throws QueryException { while(it1 != null && it2 != null) { - final int d = it1.pre - it2.pre; + final int d = it1.pre() - it2.pre(); if(d < 0) break; if(d > 0) { it2 = i2.next(); } else { - if(!mildnot(it1, it2).all.isEmpty()) break; + if(!mildnot(it1, it2).matches().isEmpty()) break; it1 = i1.next(); } } @@ -72,7 +72,7 @@ * @return specified item */ private static FTNode mildnot(final FTNode it1, final FTNode it2) { - it1.all = mildnot(it1.all, it2.all); + it1.matches(mildnot(it1.matches(), it2.matches())); return it1; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTNot.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTNot.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTNot.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTNot.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,10 +2,10 @@ import static org.basex.query.QueryText.*; -import org.basex.data.*; import org.basex.query.*; import org.basex.query.iter.*; import org.basex.query.util.*; +import org.basex.query.util.ft.*; import org.basex.query.value.node.*; import org.basex.query.var.*; import org.basex.util.*; @@ -59,7 +59,7 @@ */ private static FTNode not(final FTNode item) { if(item != null) { - item.all = not(item.all); + item.matches(not(item.matches())); item.score(Scoring.not(item.score())); } return item; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTOrder.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTOrder.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTOrder.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTOrder.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,8 +2,8 @@ import static org.basex.util.Token.*; -import org.basex.data.*; import org.basex.query.*; +import org.basex.query.util.ft.*; import org.basex.query.value.node.*; import org.basex.query.var.*; import org.basex.util.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTOr.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTOr.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTOr.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTOr.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,11 +2,11 @@ import static org.basex.query.QueryText.*; -import org.basex.data.*; import org.basex.query.*; import org.basex.query.expr.*; import org.basex.query.iter.*; import org.basex.query.util.*; +import org.basex.query.util.ft.*; import org.basex.query.value.node.*; import org.basex.query.var.*; import org.basex.util.*; @@ -71,7 +71,7 @@ // find item with smallest pre value int p = -1; for(int i = 0; i < es; ++i) { - if(it[i] != null && (p == -1 || it[p].pre > it[i].pre)) p = i; + if(it[i] != null && (p == -1 || it[p].pre() > it[i].pre())) p = i; } // no items left - leave if(p == -1) return null; @@ -79,7 +79,7 @@ // merge all matches final FTNode item = it[p]; for(int i = 0; i < es; ++i) { - if(it[i] != null && p != i && item.pre == it[i].pre) { + if(it[i] != null && p != i && item.pre() == it[i].pre()) { or(item, it[i]); it[i] = ir[i].next(); } @@ -96,11 +96,12 @@ * @param i2 second item */ private static void or(final FTNode i1, final FTNode i2) { - final FTMatches all = new FTMatches((byte) Math.max(i1.all.pos, i2.all.pos)); - for(final FTMatch m : i1.all) all.add(m); - for(final FTMatch m : i2.all) all.add(m); + final FTMatches all1 = i1.matches(), all2 = i2.matches(); + final FTMatches all = new FTMatches((byte) Math.max(all1.pos, all2.pos)); + for(final FTMatch m : all1) all.add(m); + for(final FTMatch m : all2) all.add(m); i1.score(Scoring.avg(i1.score() + i2.score(), 2)); - i1.all = all; + i1.matches(all); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTScope.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTScope.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTScope.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTScope.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,8 +2,8 @@ import static org.basex.query.QueryText.*; -import org.basex.data.*; import org.basex.query.*; +import org.basex.query.util.ft.*; import org.basex.query.value.node.*; import org.basex.query.var.*; import org.basex.util.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTWeight.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTWeight.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTWeight.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTWeight.java 2015-07-14 10:54:40.000000000 +0000 @@ -73,7 +73,7 @@ if(item == null) return null; final double d = toDouble(weight, qc); if(Math.abs(d) > 1000) throw FTWEIGHT_X.get(info, d); - if(d == 0) item.all.size(0); + if(d == 0) item.matches().reset(); item.score(item.score() * d); return item; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTWindow.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTWindow.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTWindow.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTWindow.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,9 +1,9 @@ package org.basex.query.expr.ft; -import org.basex.data.*; import org.basex.query.*; import org.basex.query.expr.*; import org.basex.query.util.*; +import org.basex.query.util.ft.*; import org.basex.query.value.node.*; import org.basex.query.var.*; import org.basex.util.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTWords.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTWords.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ft/FTWords.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ft/FTWords.java 2015-07-14 10:54:40.000000000 +0000 @@ -10,6 +10,7 @@ import org.basex.query.expr.*; import org.basex.query.iter.*; import org.basex.query.util.*; +import org.basex.query.util.ft.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -36,9 +37,9 @@ Expr query; /** Minimum and maximum occurrences. */ Expr[] occ; - /** Full-text tokenizer. */ - private FTTokenizer ftt; + FTTokenizer ftt; + /** Data reference. */ private Data data; /** Statically evaluated query tokens. */ @@ -349,15 +350,15 @@ fto.isSet(ST) && md.stemming != fto.is(ST) || fto.ln != null && !fto.ln.equals(md.language)) return false; + // adopt database options to tokenizer + fto.copy(md); + // estimate costs if text is not known at compile time if(tokens == null) { ii.costs = Math.max(2, data.meta.size / 30); return true; } - // adopt database options to tokenizer - fto.copy(md); - // summarize number of hits; break loop if no hits are expected final FTLexer ft = new FTLexer(fto); ii.costs = 0; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/gflwor/For.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/gflwor/For.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/gflwor/For.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/gflwor/For.java 2015-07-14 10:54:40.000000000 +0000 @@ -208,7 +208,7 @@ try { qc.value = null; // assign type of iterated items to context expression - final Context c = new Context(info); + final ContextValue c = new ContextValue(info); c.seqType(expr.seqType().type.seqType()); final Expr r = ex.inline(qc, scp, var, c); if(r != null) pred = r; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/gflwor/GFLWOR.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/gflwor/GFLWOR.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/gflwor/GFLWOR.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/gflwor/GFLWOR.java 2015-07-14 10:54:40.000000000 +0000 @@ -42,37 +42,58 @@ this.ret = ret; } + /** + * Creates a new evaluator for this FLWOR expression. + * @return the evaluator + */ + private Eval newEval() { + Eval e = new StartEval(); + for(final Clause cls : clauses) e = cls.eval(e); + return e; + } + @Override - public Iter iter(final QueryContext qc) { - // Start evaluator, doing nothing, once. - Eval e = new Eval() { - /** First-evaluation flag. */ - private boolean first = true; - @Override - public boolean next(final QueryContext q) { - if(!first) return false; - first = false; - return true; + public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { + Item out = null; + for(final Eval eval = newEval(); eval.next(qc);) { + final Item it = ret.item(qc, ii); + if(it != null) { + if(out != null) throw QueryError.SEQFOUND_X.get(ii, ValueBuilder.concat(out, it)); + out = it; } - }; + } + return out; + } - for(final Clause cls : clauses) e = cls.eval(e); - final Eval ev = e; + @Override + public Value value(final QueryContext qc) throws QueryException { + final Eval eval = newEval(); + if(!eval.next(qc)) return Empty.SEQ; + final Value v1 = ret.value(qc); + if(!eval.next(qc)) return v1; + + final ValueBuilder vb = new ValueBuilder().add(v1); + do { + vb.add(ret.value(qc)); + } while(eval.next(qc)); + return vb.value(); + } + @Override + public Iter iter(final QueryContext qc) { return new Iter() { + /** Clause evaluator. */ + private final Eval ev = newEval(); /** Return iterator. */ private Iter sub = Empty.ITER; - /** If the iterator has been emptied. */ - private boolean drained; @Override public Item next() throws QueryException { - if(drained) return null; - while(true) { + for(;;) { final Item it = sub.next(); qc.checkStop(); if(it != null) return it; if(!ev.next(qc)) { - drained = true; + sub = null; return null; } sub = ret.iter(qc); @@ -83,22 +104,25 @@ @Override public Expr compile(final QueryContext qc, final VarScope scp) throws QueryException { - int i = 0; + final ListIterator iter = clauses.listIterator(); + try { + while(iter.hasNext()) iter.next().compile(qc, scp); + } catch(final QueryException qe) { + iter.remove(); + clauseError(qe, iter); + } + try { - for(final Clause clause : clauses) { - clause.compile(qc, scp); - i++; - } ret = ret.compile(qc, scp); } catch(final QueryException qe) { - clauseError(qe, i); + clauseError(qe, iter); } + return optimize(qc, scp); } @Override public Expr optimize(final QueryContext qc, final VarScope scp) throws QueryException { - // split combined where clauses final ListIterator iter = clauses.listIterator(); while(iter.hasNext()) { final Clause clause = iter.next(); @@ -352,8 +376,8 @@ || expr instanceof AxisPath && ((AxisPath) expr).cheap()) { qc.compInfo(QueryText.OPTINLINE, lt.var); - inline(qc, scp, lt.var, lt.inlineExpr(qc, scp), next); - iter.remove(); + inline(qc, scp, lt.var, lt.inlineExpr(qc, scp), iter); + clauses.remove(lt); changing = changed = true; // continue from the beginning as clauses below could have been deleted break; @@ -668,7 +692,7 @@ @Override public Expr inline(final QueryContext qc, final VarScope scp, final Var var, final Expr ex) throws QueryException { - return inline(qc, scp, var, ex, 0) ? optimize(qc, scp) : null; + return inline(qc, scp, var, ex, clauses.listIterator()) ? optimize(qc, scp) : null; } /** @@ -677,15 +701,14 @@ * @param scp variable scope * @param var variable * @param ex expression to inline - * @param pos clause position + * @param iter iterator at the position of the first clause to inline into * @return if changes occurred * @throws QueryException query exception */ private boolean inline(final QueryContext qc, final VarScope scp, final Var var, final Expr ex, - final int pos) throws QueryException { + final ListIterator iter) throws QueryException { boolean changed = false; - final ListIterator iter = clauses.listIterator(pos); while(iter.hasNext()) { final Clause clause = iter.next(); try { @@ -695,7 +718,8 @@ iter.set(cl); } } catch(final QueryException qe) { - return clauseError(qe, iter.previousIndex() + 1); + iter.remove(); + return clauseError(qe, iter); } } @@ -706,7 +730,7 @@ ret = rt; } } catch(final QueryException qe) { - return clauseError(qe, clauses.size()); + return clauseError(qe, iter); } return changed; @@ -715,12 +739,13 @@ /** * Tries to recover from a compile-time exception inside a FLWOR clause. * @param qe thrown exception - * @param idx index of the throwing clause, size of {@link #clauses} for return clause + * @param iter iterator positioned where the failing clause was before * @return {@code true} if the GFLWOR expression has to stay * @throws QueryException query exception if the whole expression fails */ - private boolean clauseError(final QueryException qe, final int idx) throws QueryException { - final ListIterator iter = clauses.listIterator(idx); + private boolean clauseError(final QueryException qe, final ListIterator iter) + throws QueryException { + // check if an outer clause can prevent the error while(iter.hasPrevious()) { final Clause b4 = iter.previous(); if(b4 instanceof For || b4 instanceof Window || b4 instanceof Where) { @@ -733,6 +758,8 @@ return true; } } + + // error will always be thrown throw qe; } @@ -811,7 +838,7 @@ * @author BaseX Team 2005-15, BSD License * @author Leo Woerteler */ - interface Eval { + abstract static class Eval { /** * Makes the next evaluation step if available. This method is guaranteed * to not be called again if it has once returned {@code false}. @@ -819,7 +846,19 @@ * @return {@code true} if step was made, {@code false} if no more results exist * @throws QueryException evaluation exception */ - boolean next(final QueryContext qc) throws QueryException; + abstract boolean next(final QueryContext qc) throws QueryException; + } + + /** Start evaluator, doing nothing, once. */ + private static final class StartEval extends Eval { + /** First-evaluation flag. */ + private boolean first = true; + @Override + public boolean next(final QueryContext q) { + if(!first) return false; + first = false; + return true; + } } /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/gflwor/GroupBy.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/gflwor/GroupBy.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/gflwor/GroupBy.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/gflwor/GroupBy.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,9 +8,9 @@ import org.basex.query.expr.*; import org.basex.query.expr.gflwor.GFLWOR.Clause; import org.basex.query.expr.gflwor.GFLWOR.Eval; -import org.basex.query.iter.*; import org.basex.query.util.*; import org.basex.query.util.collation.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.query.value.seq.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/gflwor/Let.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/gflwor/Let.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/gflwor/Let.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/gflwor/Let.java 2015-07-14 10:54:40.000000000 +0000 @@ -152,7 +152,7 @@ } /** Evaluator for a block of {@code let} expressions. */ - private static class LetEval implements Eval { + private static class LetEval extends Eval { /** Let expressions of the current block, in declaration order. */ private final ArrayList lets; /** Sub-evaluator. */ @@ -170,7 +170,7 @@ } @Override - public boolean next(final QueryContext qc) throws QueryException { + boolean next(final QueryContext qc) throws QueryException { if(!sub.next(qc)) return false; for(final Let let : lets) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/gflwor/Window.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/gflwor/Window.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/gflwor/Window.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/gflwor/Window.java 2015-07-14 10:54:40.000000000 +0000 @@ -12,6 +12,7 @@ import org.basex.query.expr.gflwor.GFLWOR.Eval; import org.basex.query.iter.*; import org.basex.query.util.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.query.value.seq.*; @@ -117,8 +118,7 @@ // find end item if(fst != null) { - final ValueBuilder window = new ValueBuilder( - new Item[] {fst, null, null, null}, 1); + final ValueBuilder window = new ValueBuilder().add(fst); final Item[] st = vals == null ? new Item[] { curr, prev, next } : vals; final long ps = vals == null ? p : spos; vals = null; @@ -343,8 +343,9 @@ @Override void calcSize(final long[] minMax) { + // number of results cannot be anticipated minMax[0] = 0; - if(expr.isEmpty()) minMax[1] = 0; + minMax[1] = expr.isEmpty() ? 0 : -1; } @Override @@ -542,7 +543,7 @@ * @author BaseX Team 2005-15, BSD License * @author Leo Woerteler */ - private abstract class WindowEval implements Eval { + private abstract class WindowEval extends Eval { /** Expression iterator. */ private Iter iter; /** Previous item. */ diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/IndexAccess.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/IndexAccess.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/IndexAccess.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/IndexAccess.java 2015-07-14 10:54:40.000000000 +0000 @@ -13,7 +13,7 @@ */ public abstract class IndexAccess extends Simple { /** Index context. */ - public final IndexContext ictx; + final IndexContext ictx; /** * Constructor. diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/InterSect.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/InterSect.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/InterSect.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/InterSect.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,6 +2,7 @@ import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.query.var.*; @@ -31,24 +32,24 @@ } @Override - protected NodeSeqBuilder eval(final Iter[] iter) throws QueryException { - NodeSeqBuilder nc = new NodeSeqBuilder(); + protected ANodeList eval(final Iter[] iter) throws QueryException { + ANodeList list = new ANodeList(); - for(Item it; (it = iter[0].next()) != null;) nc.add(toNode(it)); - final boolean db = nc.dbnodes(); + for(Item it; (it = iter[0].next()) != null;) list.add(toNode(it)); + final boolean db = list.dbnodes(); final int el = exprs.length; - for(int e = 1; e < el && nc.size() != 0; ++e) { - final NodeSeqBuilder nt = new NodeSeqBuilder().check(); + for(int e = 1; e < el && list.size() != 0; ++e) { + final ANodeList nt = new ANodeList().check(); final Iter ir = iter[e]; for(Item it; (it = ir.next()) != null;) { final ANode n = toNode(it); - final int i = nc.indexOf(n, db); + final int i = list.indexOf(n, db); if(i != -1) nt.add(n); } - nc = nt; + list = nt; } - return nc; + return list; } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/List.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/List.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/List.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/List.java 2015-07-14 10:54:40.000000000 +0000 @@ -21,7 +21,7 @@ */ public final class List extends Arr { /** Limit for the size of sequences that are materialized at compile time. */ - private static final int MAX_MAT_SIZE = 1 << 20; + private static final int MAX_MAT_SIZE = 1 << 16; /** * Constructor. * @param info input info @@ -45,6 +45,20 @@ @Override public Expr optimize(final QueryContext qc, final VarScope scp) throws QueryException { + // remove empty sequences + int p = 0; + for(final Expr e : exprs) { + if(!e.isEmpty()) exprs[p++] = e; + } + + if(p != exprs.length) { + qc.compInfo(OPTREMOVE, this, Empty.SEQ); + if(p < 2) return p == 0 ? Empty.SEQ : exprs[0]; + final Expr[] es = new Expr[p]; + System.arraycopy(exprs, 0, es, 0, p); + exprs = es; + } + // compute number of results size = 0; boolean ne = false; @@ -60,7 +74,6 @@ } if(size >= 0) { - if(size == 0 && !has(Flag.NDT) && !has(Flag.UPD)) return optPre(qc); if(allAreValues() && size <= MAX_MAT_SIZE) { Type all = null; final Value[] vs = new Value[exprs.length]; @@ -83,7 +96,7 @@ else if(all != null && all.instanceOf(AtomType.ITR)) { val = IntSeq.get(vs, s, all); } else { - final ValueBuilder vb = new ValueBuilder(s); + final ValueBuilder vb = new ValueBuilder(); for(int i = 0; i < c; i++) vb.add(vs[i]); val = vb.value(); } @@ -129,6 +142,8 @@ @Override public Value value(final QueryContext qc) throws QueryException { + // most common case + if(exprs.length == 2) return ValueBuilder.concat(qc.value(exprs[0]), qc.value(exprs[1])); final ValueBuilder vb = new ValueBuilder(); for(final Expr e : exprs) vb.add(qc.value(e)); return vb.value(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Lookup.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Lookup.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Lookup.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Lookup.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,11 +5,15 @@ import java.util.*; import org.basex.query.*; +import org.basex.query.expr.gflwor.*; +import org.basex.query.func.*; import org.basex.query.iter.*; import org.basex.query.value.*; import org.basex.query.value.array.Array; import org.basex.query.value.item.*; import org.basex.query.value.map.Map; +import org.basex.query.value.seq.*; +import org.basex.query.value.type.*; import org.basex.query.var.*; import org.basex.util.*; import org.basex.util.hash.*; @@ -21,13 +25,18 @@ * @author Christian Gruen */ public final class Lookup extends Arr { + /** Static context. */ + private final StaticContext sc; + /** * Constructor. * @param info input info + * @param sc static context * @param expr expression and optional context */ - public Lookup(final InputInfo info, final Expr... expr) { + public Lookup(final InputInfo info, final StaticContext sc, final Expr... expr) { super(info, expr); + this.sc = sc; } @Override @@ -38,31 +47,115 @@ @Override public Expr optimize(final QueryContext qc, final VarScope scp) throws QueryException { - return exprs.length > 1 && (exprs[1] instanceof Map || exprs[1] instanceof Array) && - exprs[0].isValue() ? optPre(value(qc), qc) : this; + if(exprs.length != 2) return this; + + final Expr ks = exprs[0], fs = exprs[1]; + if(ks.isValue() && (fs instanceof Map || fs instanceof Array)) { + // guaranteed to be fully evaluated + return optPre(value(qc), qc); + } + + final Type tp = fs.seqType().type; + if(!(tp instanceof ArrayType || tp instanceof MapType)) return this; + + if(ks != Str.WC) { + if(fs.size() == 1 || fs.seqType().one()) { + // one function, rewrite to for-each or function call + final Expr opt = ks.size() == 1 || ks.seqType().one() + ? new DynFuncCall(info, sc, false, fs, ks) : Function.FOR_EACH.get(sc, info, exprs); + return optPre(opt, qc).optimize(qc, scp); + } + + if(ks.isValue()) { + // keys are constant, so we do not duplicate work in the inner loop + final LinkedList clauses = new LinkedList<>(); + final Var f = scp.newLocal(qc, QNm.get("f"), null, false); + clauses.add(new For(f, null, null, fs, false, info)); + final Var k = scp.newLocal(qc, QNm.get("k"), null, false); + clauses.add(new For(k, null, null, ks, false, info)); + final VarRef rf = new VarRef(info, f), rk = new VarRef(info, k); + final DynFuncCall ret = new DynFuncCall(info, sc, false, rf, rk); + return optPre(new GFLWOR(info, clauses, ret), qc).optimize(qc, scp); + } + } + + return this; + } + + @Override + public Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); } @Override - public ValueIter iter(final QueryContext qc) throws QueryException { + public Value value(final QueryContext qc) throws QueryException { + if(exprs[0] == Str.WC) return wildCard(qc); + if(exprs[0] instanceof Item) return lookup((Item) exprs[0], qc); + final ValueBuilder vb = new ValueBuilder(); - for(final Item ctx : exprs.length == 1 ? ctxValue(qc) : qc.value(exprs[1])) { - final boolean map = ctx instanceof Map, array = ctx instanceof Array; - if(!map && !array) throw LOOKUP_X.get(info, ctx); + final Iter iter = exprs.length == 1 ? ctxValue(qc).iter() : qc.iter(exprs[1]); + for(Item ctx; (ctx = iter.next()) != null;) { + final Iter keys = exprs[0].iter(qc); + final FItem f = mapOrArray(ctx); + for(Item k; (k = keys.next()) != null;) vb.add(f.invokeValue(qc, info, k)); + } + return vb.value(); + } - final FItem f = (FItem) ctx; - if(exprs[0] == Str.WC) { - if(map) { - for(final Value v : ((Map) f).values()) vb.add(v); - } else { - final Iterator iter = ((Array) f).members(); - while(iter.hasNext()) vb.add(iter.next()); - } + /** + * Checks if the given item is either a map or an array. + * @param it item to check + * @return cast item if check succeeded + * @throws QueryException if the item is neither a map nor an array + */ + private FItem mapOrArray(final Item it) throws QueryException { + final boolean map = it instanceof Map, array = it instanceof Array; + if(!map && !array) throw LOOKUP_X.get(info, it); + return (FItem) it; + } + + /** + * Evaluates the {@code ?*} construct. + * @param qc query context + * @return all entries of all input values + * @throws QueryException query exception + */ + private Value wildCard(final QueryContext qc) throws QueryException { + final ValueBuilder vb = new ValueBuilder(); + for(final Item ctx : exprs.length == 1 ? ctxValue(qc) : qc.value(exprs[1])) { + if(ctx instanceof Map) { + for(final Value v : ((Map) ctx).values()) vb.add(v); + } else if(ctx instanceof Array) { + for(final Value val : ((Array) ctx).members()) vb.add(val); } else { - final Iter ir = qc.iter(exprs[0]); - for(Item it; (it = ir.next()) != null;) vb.add(f.invokeValue(qc, info, it)); + throw LOOKUP_X.get(info, ctx); } } - return vb; + return vb.value(); + } + + /** + * Fast path for the case where the key is a single item. + * @param key key to look up + * @param qc query context + * @return resulting value + * @throws QueryException query exception + */ + private Value lookup(final Item key, final QueryContext qc) throws QueryException { + final Iter iter = exprs.length == 1 ? ctxValue(qc).iter() : qc.iter(exprs[1]); + + final Item fst = iter.next(); + if(fst == null) return Empty.SEQ; + + final Value fstVal = mapOrArray(fst).invokeValue(qc, info, key); + Item it = iter.next(); + if(it == null) return fstVal; + + final ValueBuilder vb = new ValueBuilder().add(fstVal); + do { + vb.add(mapOrArray(it).invokeValue(qc, info, key)); + } while((it = iter.next()) != null); + return vb.value(); } @Override @@ -72,13 +165,25 @@ @Override public Lookup copy(final QueryContext qc, final VarScope scp, final IntObjMap vs) { - return copyType(new Lookup(info, copyAll(qc, scp, vs, exprs))); + return copyType(new Lookup(info, sc, copyAll(qc, scp, vs, exprs))); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); - if(exprs.length > 1) sb.append(exprs[1]).append(' '); - return sb.append("? ").append(exprs[0]).toString(); + if(exprs.length > 1) sb.append('(').append(exprs[1]).append(')'); + final Expr key = exprs[0]; + + if(key == Str.WC) { + return sb.append("?*").toString(); + } else if(key instanceof Str) { + final Str str = (Str) key; + if(XMLToken.isNCName(str.string())) return sb.append('?').append(str.toJava()).toString(); + } else if(key instanceof Int) { + final long val = ((Int) key).itr(); + if(val >= 0) return sb.append('?').append(val).toString(); + } + + return sb.append(" ? (").append(exprs[0]).append(')').toString(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ParseExpr.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ParseExpr.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ParseExpr.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ParseExpr.java 2015-07-14 10:54:40.000000000 +0000 @@ -32,7 +32,7 @@ /** * Constructor. - * @param info input info + * @param info input info (can be {@code null} */ protected ParseExpr(final InputInfo info) { this.info = info; @@ -51,7 +51,7 @@ if(it == null || ir.size() == 1) return it; final Item n = ir.next(); if(n != null) { - final ValueBuilder vb = new ValueBuilder(3).add(it).add(n); + final ValueBuilder vb = new ValueBuilder().add(it).add(n); if(ir.next() != null) vb.add(Str.get(DOTS)); throw SEQFOUND_X.get(ii, vb.value()); } @@ -101,7 +101,7 @@ if(it != null && !(it instanceof ANode)) { final Item n = ir.next(); if(n != null) { - final ValueBuilder vb = new ValueBuilder(3).add(it).add(n); + final ValueBuilder vb = new ValueBuilder().add(it).add(n); if(ir.next() != null) vb.add(Str.get(DOTS)); throw EBV_X.get(ii, vb.value()); } @@ -444,6 +444,22 @@ } /** + * Checks if the evaluated expression yields a non-empty node or item. + * Returns the item or throws an exception. + * @param ex expression to be evaluated + * @param qc query context + * @return item + * @throws QueryException query exception + */ + protected final Item toNodeOrAtomItem(final Expr ex, final QueryContext qc) + throws QueryException { + + Item it = toItem(ex, qc); + if(!(it instanceof ANode)) it = it.atomItem(info); + return it; + } + + /** * Checks if the evaluated expression yields a non-empty item. * Returns the item or throws an exception. * @param ex expression to be evaluated diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/Axis.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/Axis.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/Axis.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/Axis.java 2015-07-14 10:54:40.000000000 +0000 @@ -17,7 +17,7 @@ /** Ancestor-or-self axis. */ ANCORSELF("ancestor-or-self", false) { @Override - AxisIter iter(final ANode n) { + BasicNodeIter iter(final ANode n) { return n.ancestorOrSelf(); } }, @@ -25,7 +25,7 @@ /** Ancestor axis. */ ANC("ancestor", false) { @Override - AxisIter iter(final ANode n) { + BasicNodeIter iter(final ANode n) { return n.ancestor(); } }, @@ -33,7 +33,7 @@ /** Attribute axis. */ ATTR("attribute", true) { @Override - AxisIter iter(final ANode n) { + BasicNodeIter iter(final ANode n) { return n.attributes(); } }, @@ -41,7 +41,7 @@ /** Child Axis. */ CHILD("child", true) { @Override - AxisIter iter(final ANode n) { + BasicNodeIter iter(final ANode n) { return n.children(); } }, @@ -49,7 +49,7 @@ /** Descendant-or-self axis. */ DESCORSELF("descendant-or-self", true) { @Override - AxisIter iter(final ANode n) { + BasicNodeIter iter(final ANode n) { return n.descendantOrSelf(); } }, @@ -57,7 +57,7 @@ /** Descendant axis. */ DESC("descendant", true) { @Override - AxisIter iter(final ANode n) { + BasicNodeIter iter(final ANode n) { return n.descendant(); } }, @@ -65,7 +65,7 @@ /** Following-Sibling axis. */ FOLLSIBL("following-sibling", false) { @Override - AxisIter iter(final ANode n) { + BasicNodeIter iter(final ANode n) { return n.followingSibling(); } }, @@ -73,7 +73,7 @@ /** Following axis. */ FOLL("following", false) { @Override - AxisIter iter(final ANode n) { + BasicNodeIter iter(final ANode n) { return n.following(); } }, @@ -81,7 +81,7 @@ /** Parent axis. */ PARENT("parent", true) { @Override - AxisIter iter(final ANode n) { + BasicNodeIter iter(final ANode n) { return n.parentIter(); } }, @@ -89,7 +89,7 @@ /** Preceding-Sibling axis. */ PRECSIBL("preceding-sibling", false) { @Override - AxisIter iter(final ANode n) { + BasicNodeIter iter(final ANode n) { return n.precedingSibling(); } }, @@ -97,7 +97,7 @@ /** Preceding axis. */ PREC("preceding", false) { @Override - AxisIter iter(final ANode n) { + BasicNodeIter iter(final ANode n) { return n.preceding(); } }, @@ -105,7 +105,7 @@ /** Step axis. */ SELF("self", true) { @Override - AxisIter iter(final ANode n) { + BasicNodeIter iter(final ANode n) { return n.self(); } }; @@ -132,7 +132,7 @@ * @param n input node * @return node iterator */ - abstract AxisIter iter(final ANode n); + abstract BasicNodeIter iter(final ANode n); @Override public String toString() { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/AxisPath.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/AxisPath.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/AxisPath.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/AxisPath.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,11 @@ package org.basex.query.expr.path; +import org.basex.query.*; import org.basex.query.expr.*; +import org.basex.query.iter.*; import org.basex.query.util.list.*; +import org.basex.query.value.*; +import org.basex.query.value.node.*; import org.basex.util.*; /** @@ -11,6 +15,21 @@ * @author Christian Gruen */ public abstract class AxisPath extends Path { + /** Caching states. */ + private enum Caching { + /** Caching is disabled. */ DISABLED, + /** Caching is possible. */ ENABLED, + /** Ready to cache. */ READY, + /** Results are cached. */ CACHED; + }; + /** Current state. */ + private Caching state; + + /** Cached result. */ + private Value cached; + /** Cached context value. */ + private Value cvalue; + /** * Constructor. * @param info input info @@ -19,9 +38,61 @@ */ AxisPath(final InputInfo info, final Expr root, final Expr... steps) { super(info, root, steps); + // cache values if expression has no free variables, is deterministic and performs no updates + state = !hasFreeVars() && !has(Flag.NDT) && !has(Flag.UPD) && !has(Flag.HOF) ? + Caching.ENABLED : Caching.DISABLED; + } + + @Override + public final Iter iter(final QueryContext qc) throws QueryException { + if(state == Caching.CACHED) { + // last result was cached + if(!sameContext(qc)) { + // disable caching if context has changed (expected to change frequently) + cached = null; + state = Caching.DISABLED; + } + } else if (state == Caching.READY) { + // values are ready to cache + if(sameContext(qc)) { + cached = nodeIter(qc).value(); + state = Caching.CACHED; + } else { + // disable caching if context has changed (expected to change frequently) + state = Caching.DISABLED; + } + } else if(state == Caching.ENABLED) { + // caching is possible: remember context value + cvalue = qc.value instanceof DBNode ? ((DBNode) qc.value).copy() : qc.value; + state = Caching.READY; + } + // return new iterator or cached values + return cached == null ? nodeIter(qc) : cached.iter(); } /** + * Checks if the specified context value is different to the cached one. + * @param qc query context + * @return result of check + */ + private boolean sameContext(final QueryContext qc) { + final Value cv = qc.value; + // context value has not changed... + if(cv == cvalue && (cv == null || cv.sameAs(cvalue))) return true; + // otherwise, if path starts with root node, compare roots of cached and new context value + return root instanceof Root && cv instanceof ANode && cvalue instanceof ANode && + ((ANode) cv).root().sameAs(((ANode) cv).root()); + } + + /** + * Returns a node iterator. + * @param qc query context + * @return iterator + * @throws QueryException query exception + */ + protected abstract NodeIter nodeIter(final QueryContext qc) throws QueryException; + + /** * Inverts a location path. * @param rt new root node * @param curr current location step diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/CachedPath.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/CachedPath.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/CachedPath.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/CachedPath.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,6 +5,7 @@ import org.basex.query.*; import org.basex.query.expr.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -19,9 +20,6 @@ * @author Christian Gruen */ final class CachedPath extends AxisPath { - /** Flag for result caching. */ - private boolean cache; - /** * Constructor. * @param info input info @@ -30,16 +28,14 @@ */ CachedPath(final InputInfo info, final Expr root, final Expr... steps) { super(info, root, steps); - // analyze if result set can be cached: no root node, no variables... - cache = root != null && !hasFreeVars(); } @Override - public Iter iter(final QueryContext qc) throws QueryException { + protected NodeIter nodeIter(final QueryContext qc) throws QueryException { final long cp = qc.pos, cs = qc.size; final Value cv = qc.value, r = root != null ? qc.value(root) : cv; + final ANodeList list = new ANodeList().check(); try { - final NodeSeqBuilder nb = new NodeSeqBuilder().check(); if(r != null) { final Iter ir = qc.iter(r); for(Item it; (it = ir.next()) != null;) { @@ -47,40 +43,40 @@ if(root != null && !(it instanceof ANode)) throw PATHNODE_X_X_X.get(info, steps[0], it.type, it); qc.value = it; - iter(0, nb, qc); + iter(0, list, qc); } } else { qc.value = null; - iter(0, nb, qc); + iter(0, list, qc); } - return nb.sort(); } finally { qc.value = cv; qc.size = cs; qc.pos = cp; } + return list.iter(); } /** * Recursive step iterator. - * @param l current step - * @param nc node cache + * @param step current step + * @param list node cache * @param qc query context * @throws QueryException query exception */ - private void iter(final int l, final NodeSeqBuilder nc, final QueryContext qc) + private void iter(final int step, final ANodeList list, final QueryContext qc) throws QueryException { // cast is safe (steps will always return a {@link NodeIter} instance) - final NodeIter ni = (NodeIter) qc.iter(steps[l]); - final boolean more = l + 1 != steps.length; + final NodeIter ni = (NodeIter) qc.iter(steps[step]); + final boolean more = step + 1 != steps.length; for(ANode node; (node = ni.next()) != null;) { if(more) { qc.value = node; - iter(l + 1, nc, qc); + iter(step + 1, list, qc); } else { qc.checkStop(); - nc.add(node); + list.add(node); } } } @@ -88,8 +84,6 @@ @Override public AxisPath copy(final QueryContext qc, final VarScope scp, final IntObjMap vs) { final Expr rt = root == null ? null : root.copy(qc, scp, vs); - final CachedPath ap = copyType(new CachedPath(info, rt, Arr.copyAll(qc, scp, vs, steps))); - ap.cache = cache; - return ap; + return copyType(new CachedPath(info, rt, Arr.copyAll(qc, scp, vs, steps))); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/CachedStep.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/CachedStep.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/CachedStep.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/CachedStep.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,6 +3,7 @@ import org.basex.query.*; import org.basex.query.expr.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.query.var.*; @@ -28,35 +29,35 @@ } @Override - public NodeSeqBuilder iter(final QueryContext qc) throws QueryException { + public NodeIter iter(final QueryContext qc) throws QueryException { // evaluate step - final AxisIter ai = axis.iter(checkNode(qc)); - final NodeSeqBuilder nc = new NodeSeqBuilder(); - for(ANode n; (n = ai.next()) != null;) { - if(test.eq(n)) nc.add(n.finish()); + final BasicNodeIter iter = axis.iter(checkNode(qc)); + final ANodeList list = new ANodeList(); + for(ANode n; (n = iter.next()) != null;) { + if(test.eq(n)) list.add(n.finish()); } // evaluate predicates final boolean scoring = qc.scoring; for(final Expr pred : preds) { - final long nl = nc.size(); + final long nl = list.size(); qc.size = nl; qc.pos = 1; int c = 0; for(int n = 0; n < nl; ++n) { - final ANode node = nc.get(n); + final ANode node = list.get(n); qc.value = node; final Item tst = pred.test(qc, info); if(tst != null) { // assign score value if(scoring) node.score(tst.score()); - nc.nodes[c++] = node; + list.set(c++, node); } qc.pos++; } - nc.size(c); + list.size(c); } - return nc; + return list.iter(); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/DocTest.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/DocTest.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/DocTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/DocTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,12 +11,12 @@ * @author Christian Gruen */ public final class DocTest extends Test { - /** Optional child test. */ + /** Child test. */ private final Test test; /** * Constructor. - * @param test child test (may be {@code null}) + * @param test child test */ public DocTest(final Test test) { this.test = test; @@ -31,8 +31,14 @@ @Override public boolean eq(final ANode node) { if(node.type != NodeType.DOC) return false; - final AxisMoreIter ai = node.children(); - return ai.more() && test.eq(ai.next()) && !ai.more(); + final BasicNodeIter iter = node.children(); + boolean found = false; + for(ANode n; (n = iter.next()) != null;) { + if(n.type == NodeType.COM || n.type == NodeType.PI) continue; + if(found || !test.eq(n)) return false; + found = true; + } + return true; } @Override @@ -55,8 +61,6 @@ @Override public String toString() { - final StringBuilder sb = new StringBuilder().append(type).append('('); - if(test != null) sb.append(test); - return sb.append(')').toString(); + return test.toString(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/InvDocTest.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/InvDocTest.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/InvDocTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/InvDocTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -46,13 +46,13 @@ // adopt nodes from existing sequence if(rt instanceof DBNodeSeq) { final DBNodeSeq seq = (DBNodeSeq) rt; - return seq.all ? Test.DOC : new InvDocTest(new IntList(seq.pres), data); + return seq.all() ? Test.DOC : new InvDocTest(new IntList(seq.pres()), data); } // loop through all documents and add pre values of documents // not more than 2^31 documents supported final IntList il = new IntList((int) rt.size()); - for(final Item it : rt) il.add(((DBNode) it).pre); + for(final Item it : rt) il.add(((DBNode) it).pre()); return new InvDocTest(il, data); } @@ -62,7 +62,7 @@ if(!(node instanceof DBNode)) return false; // ensure that the pre value is contained in the target documents final DBNode db = (DBNode) node; - return data == db.data && pres.contains(db.pre); + return data == db.data() && pres.contains(db.pre()); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/IterLastStep.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/IterLastStep.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/IterLastStep.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/IterLastStep.java 2015-07-14 10:54:40.000000000 +0000 @@ -37,7 +37,7 @@ stop = true; // return last items - final AxisIter iter = axis.iter(checkNode(qc)); + final BasicNodeIter iter = axis.iter(checkNode(qc)); ANode litem = null; final Test tst = test; for(ANode item; (item = iter.next()) != null;) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/IterPath.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/IterPath.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/IterPath.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/IterPath.java 2015-07-14 10:54:40.000000000 +0000 @@ -32,7 +32,7 @@ } @Override - public NodeIter iter(final QueryContext qc) { + protected NodeIter nodeIter(final QueryContext qc) { return new NodeIter() { final boolean r = root != null; final int sz = steps.length + (r ? 1 : 0); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/IterPosStep.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/IterPosStep.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/IterPosStep.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/IterPosStep.java 2015-07-14 10:54:40.000000000 +0000 @@ -35,13 +35,13 @@ final Pos[] posExpr = new Pos[preds.length]; final long[] cPos = new long[preds.length]; boolean skip; - AxisIter ai; + BasicNodeIter iter; @Override public ANode next() throws QueryException { if(skip) return null; - if(ai == null) { - ai = axis.iter(checkNode(qc)); + if(iter == null) { + iter = axis.iter(checkNode(qc)); final int pl = preds.length; for(int p = 0; p < pl; p++) { final Expr pred = preds[p]; @@ -49,7 +49,9 @@ posExpr[p] = (Pos) pred; } else if(num(pred)) { // pre-evaluate numeric position - final double dbl = toDouble(pred, qc); + final Item it = pred.atomItem(qc, info); + if(it == null) return null; + final double dbl = toDouble(it); final long lng = (long) dbl; if(dbl != lng) return null; final Expr e = Pos.get(lng, info); @@ -59,7 +61,7 @@ } } - for(ANode node; (node = ai.next()) != null;) { + for(ANode node; (node = iter.next()) != null;) { qc.checkStop(); if(test.eq(node) && preds(node)) return node.finish(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/IterStep.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/IterStep.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/IterStep.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/IterStep.java 2015-07-14 10:54:40.000000000 +0000 @@ -30,12 +30,12 @@ @Override public NodeIter iter(final QueryContext qc) { return new NodeIter() { - AxisIter ai; + BasicNodeIter iter; @Override public ANode next() throws QueryException { - if(ai == null) ai = axis.iter(checkNode(qc)); - for(ANode node; (node = ai.next()) != null;) { + if(iter == null) iter = axis.iter(checkNode(qc)); + for(ANode node; (node = iter.next()) != null;) { qc.checkStop(); if(test.eq(node) && preds(node, qc)) return node.finish(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/MixedPath.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/MixedPath.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/MixedPath.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/MixedPath.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,6 +5,7 @@ import org.basex.query.*; import org.basex.query.expr.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -36,9 +37,24 @@ @Override public Iter iter(final QueryContext qc) throws QueryException { - // creates an iterator from the root value - final Value v = root != null ? qc.value(root) : ctxValue(qc); - Iter res = v.iter(); + Iter iter; + long sz; + if(root != null) { + final Iter rt = qc.iter(root); + final long s = rt.size(); + if(s >= 0) { + iter = rt; + sz = s; + } else { + final Value val = rt.value(); + iter = val.iter(); + sz = val.size(); + } + } else { + final Value rt = ctxValue(qc); + iter = rt.iter(); + sz = rt.size(); + } final Value cv = qc.value; final long cs = qc.size; @@ -48,15 +64,15 @@ final int sl = steps.length; for(int s = 0; s < sl; s++) { final Expr step = steps[s]; - final ValueBuilder vb = new ValueBuilder(); + final ItemList cache = new ItemList(); // map operator: don't remove duplicates and check for nodes - qc.size = res.size(); + qc.size = sz; qc.pos = 1; // loop through all input items - int nodes = 0; - for(Item it; (it = res.next()) != null;) { + long nodes = 0; + for(Item it; (it = iter.next()) != null;) { if(!(it instanceof ANode)) throw PATHNODE_X_X_X.get(info, step, it.type, it); qc.value = it; @@ -64,32 +80,32 @@ final Iter ir = qc.iter(step); for(Item i; (i = ir.next()) != null;) { if(i instanceof ANode) nodes++; - vb.add(i); + cache.add(i); } qc.pos++; } - final long vs = vb.size(); + final long vs = cache.size(); if(nodes < vs) { // check if both nodes and atomic values occur in last result if(nodes > 0) throw EVALNODESVALS.get(info); // check if input for next axis step consists items other than nodes if(s + 1 < sl) { - final Item it = vb.get(0); + final Item it = cache.get(0); throw PATHNODE_X_X_X.get(info, steps[s + 1], it.type, it); } } if(nodes == vs) { // remove potential duplicates from node sets - final NodeSeqBuilder nc = new NodeSeqBuilder().check(); - for(Item it; (it = vb.next()) != null;) nc.add((ANode) it); - res = nc.value().cache(); + final ANodeList list = new ANodeList().check(); + for(final Item nd : cache) list.add((ANode) nd); + iter = list.iter(); } else { - res = vb; + iter = cache.iter(); } } - return res; + return iter; } finally { qc.value = cv; qc.size = cs; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/NameTest.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/NameTest.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/NameTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/NameTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -51,7 +51,7 @@ if(data == null) return true; // skip optimizations if more than one namespace is defined in the database - final byte[] dataNS = data.nspaces.globalNS(); + final byte[] dataNS = data.nspaces.globalUri(); if(dataNS == null) return true; // check if test may yield results diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/NodeTest.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/NodeTest.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/NodeTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/NodeTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -14,15 +14,13 @@ public final class NodeTest extends Test { /** Extended type. */ private final Type ext; - /** Strip flag (only relevant if specified type is {@code xs:untyped}). */ - private final boolean strip; /** * Convenience constructor for element tests. * @param nm node name */ public NodeTest(final QNm nm) { - this(NodeType.ELM, nm, null, false); + this(NodeType.ELM, nm, null); } /** @@ -31,7 +29,7 @@ * @param nm optional node name */ public NodeTest(final NodeType nt, final QNm nm) { - this(nt, nm, null, false); + this(nt, nm, null); } /** @@ -39,32 +37,28 @@ * @param type node type * @param name optional node name * @param ext extended node type - * @param strip strip flag; only relevant if specified type is {@code xs:untyped} */ - public NodeTest(final NodeType type, final QNm name, final Type ext, final boolean strip) { + public NodeTest(final NodeType type, final QNm name, final Type ext) { this.type = type; this.name = name; this.ext = ext; - this.strip = strip; } @Override public Test copy() { - return new NodeTest(type, name, ext, strip); + return new NodeTest(type, name, ext); } @Override public boolean eq(final ANode node) { return node.type == type && (name == null || node.qname(tmpq).eq(name)) && - (ext == null || ext == AtomType.ATY || - (node instanceof DBNode || strip) && ext == AtomType.UTY || - type == NodeType.ATT && (ext == AtomType.AST || - ext == AtomType.AAT || ext == AtomType.ATM)); + (ext == null || ext == AtomType.ATY || ext == AtomType.UTY || + type == NodeType.ATT && (ext == AtomType.AST || ext == AtomType.AAT || ext == AtomType.ATM)); } @Override - public Test intersect(final Test other) { + public NodeTest intersect(final Test other) { if(other instanceof NodeTest) { final NodeTest o = (NodeTest) other; if(type != null && o.type != null && type != o.type) return null; @@ -73,7 +67,7 @@ final QNm n = name != null ? name : o.name; final boolean both = ext != null && o.ext != null; final Type e = ext == null ? o.ext : o.ext == null ? ext : ext.intersect(o.ext); - return both && e == null ? null : new NodeTest(nt, n, e, strip || o.strip); + return both && e == null ? null : new NodeTest(nt, n, e); } if(other instanceof KindTest) { return type.instanceOf(other.type) ? this : null; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/Path.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/Path.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/Path.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/Path.java 2015-07-14 10:54:40.000000000 +0000 @@ -74,26 +74,26 @@ rt = path.root; } // remove redundant context reference - if(rt instanceof Context) rt = null; + if(rt instanceof ContextValue) rt = null; // add steps of input array for(final Expr expr : steps) { Expr step = expr; - if(step instanceof Context) { + if(step instanceof ContextValue) { // remove redundant context references if(sl > 1) continue; // single step: rewrite to axis step (required to sort results of path) - step = Step.get(((Context) step).info, SELF, Test.NOD); + step = Step.get(((ContextValue) step).info, SELF, Test.NOD); } else if(step instanceof Filter) { // rewrite filter to axis step final Filter f = (Filter) step; - if(f.root instanceof Context) { + if(f.root instanceof ContextValue) { step = Step.get(f.info, SELF, Test.NOD, f.preds); } } else if(step instanceof Path) { // rewrite path to axis steps final Path p = (Path) step; - if(p.root != null && !(p.root instanceof Context)) stps.add(p.root); + if(p.root != null && !(p.root instanceof ContextValue)) stps.add(p.root); final int pl = p.steps.length - 1; for(int i = 0; i < pl; i++) stps.add(p.steps[i]); step = p.steps[pl]; @@ -123,7 +123,7 @@ public final Expr compile(final QueryContext qc, final VarScope scp) throws QueryException { if(root != null) root = root.compile(qc, scp); // no steps - if(steps.length == 0) return root == null ? new Context(info) : root; + if(steps.length == 0) return root == null ? new ContextValue(info) : root; final Value init = qc.value, cv = initial(qc); qc.value = cv; @@ -231,9 +231,12 @@ @Override public final boolean has(final Flag flag) { - // first step or root expression will be used as context + // context dependency. no root exists, or if it depends on context. Example: text(); ./abc if(flag == Flag.CTX) return root == null || root.has(flag); - for(final Expr s : steps) if(s.has(flag)) return true; + // positional test. may only occur in root node, not in first step. Example: position()/a + if(flag != Flag.POS) { + for(final Expr step : steps) if(step.has(flag)) return true; + } return root != null && root.has(flag); } @@ -377,9 +380,9 @@ // current context value final Value v = qc != null ? qc.value : null; // no root or context expression: return context - if(root == null || root instanceof Context) return v; + if(root == null || root instanceof ContextValue) return v; // root reference - if(root instanceof Root) return v instanceof Item ? Root.root(v) : v; + if(root instanceof Root) return v instanceof ANode ? ((ANode) v).root() : v; // root is value: return root if(root.isValue()) return (Value) root; // data reference @@ -540,7 +543,7 @@ 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; + if(data == null || !data.meta.uptodate || data.nspaces.globalUri() == null) return this; Path path = this; final int sl = steps.length; @@ -551,7 +554,7 @@ // ignore axes other than descendant, or numeric predicates final Step curr = axisStep(s); - if(curr == null || curr.axis != DESC || curr.has(Flag.FCS)) continue; + if(curr == null || curr.axis != DESC || curr.has(Flag.POS)) continue; // check if child steps can be retrieved for current step ArrayList nodes = pathNodes(data, s); @@ -637,7 +640,7 @@ // only accept descendant steps without positional predicates // Example for position predicate: child:x[1] != parent::x[1] final Step step = axisStep(s); - if(step == null || !step.axis.down || step.has(Flag.FCS)) break; + if(step == null || !step.axis.down || step.has(Flag.POS)) break; // check if path is iterable (i.e., will be duplicate-free) final boolean iter = pathNodes(data, s) != null; @@ -784,7 +787,7 @@ continue; } // //(X, Y)[text()] -> (/descendant::X | /descendant::Y)[text()] - if(next instanceof Filter && !next.has(Flag.FCS)) { + if(next instanceof Filter && !next.has(Flag.POS)) { final Filter f = (Filter) next; e = mergeList(f.root); if(e != null) { @@ -843,8 +846,8 @@ */ private static boolean simpleChild(final Expr expr) { if(expr instanceof Step) { - final Step n = (Step) expr; - if(n.axis == CHILD && !n.has(Flag.FCS)) return true; + final Step step = (Step) expr; + if(step.axis == CHILD && !step.has(Flag.POS)) return true; } return false; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/Step.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/Step.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/path/Step.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/path/Step.java 2015-07-14 10:54:40.000000000 +0000 @@ -53,7 +53,7 @@ for(final Expr pred : preds) { if(pred instanceof Pos || num(pred)) { pos = true; - } else if(pred.seqType().mayBeNumber() || pred.has(Flag.FCS)) { + } else if(pred.seqType().mayBeNumber() || pred.has(Flag.POS)) { // positional checks may be nested or non-deterministic: choose full evaluation return new CachedStep(info, axis, test, preds); } @@ -126,7 +126,7 @@ */ final ArrayList nodes(final ArrayList nodes, final Data data) { // skip steps with predicates or different namespaces - if(preds.length != 0 || data.nspaces.globalNS() == null) return null; + if(preds.length != 0 || data.nspaces.globalUri() == null) return null; // check restrictions on node type int kind = -1, name = 0; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Pos.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Pos.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Pos.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Pos.java 2015-07-14 10:54:40.000000000 +0000 @@ -143,7 +143,7 @@ @Override public boolean has(final Flag flag) { - return flag == Flag.FCS; + return flag == Flag.POS; } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Pragma.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Pragma.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Pragma.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Pragma.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,6 +3,7 @@ import static org.basex.query.QueryText.*; import org.basex.query.*; +import org.basex.query.expr.Expr.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.util.*; @@ -21,12 +22,12 @@ /** * Constructor. - * @param n name of pragma - * @param v optional value + * @param name name of pragma + * @param value optional value */ - Pragma(final QNm n, final byte[] v) { - name = n; - value = v; + Pragma(final QNm name, final byte[] value) { + this.name = name; + this.value = value; } @Override @@ -48,6 +49,14 @@ */ abstract void finish(final QueryContext qc); + /** + * Indicates if an expression has the specified compiler property. + * @param flag flag to be checked + * @return result of check + * @see Expr#has + */ + public abstract boolean has(final Flag flag); + @Override public final String toString() { final TokenBuilder tb = new TokenBuilder(PRAGMA + ' ' + name + ' '); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Preds.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Preds.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Preds.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Preds.java 2015-07-14 10:54:40.000000000 +0000 @@ -89,7 +89,7 @@ } } } else if(pred instanceof And) { - if(!pred.has(Flag.FCS)) { + if(!pred.has(Flag.POS)) { // replace AND expression with predicates (don't swap position tests) qc.compInfo(OPTPRED, pred); final Expr[] and = ((Arr) pred).exprs; @@ -205,7 +205,7 @@ final Expr pred = preds[0]; // a[.] -> a - if(pred instanceof Context) return root; + if(pred instanceof ContextValue) return root; if(!pred.seqType().mayBeNumber()) { // a[b] -> a/b @@ -218,7 +218,7 @@ if(!expr2.has(Flag.CTX)) { Expr path = null; // a[. = 'x'] -> a = 'x' - if(expr1 instanceof Context) path = root; + if(expr1 instanceof ContextValue) path = root; // a[text() = 'x'] -> a/text() = 'x' if(expr1 instanceof Path) path = Path.get(info, root, expr1).optimize(qc, scp); if(path != null) return new CmpG(path, expr2, cmp.op, cmp.coll, cmp.sc, cmp.info); @@ -233,7 +233,7 @@ final Expr expr = cmp.expr; Expr path = null; // a[. contains text 'x'] -> a contains text 'x' - if(expr instanceof Context) path = root; + if(expr instanceof ContextValue) path = root; // [text() contains text 'x'] -> a/text() contains text 'x' if(expr instanceof Path) path = Path.get(info, root, expr).optimize(qc, scp); if(path != null) return new FTContains(path, ftexpr, cmp.info); @@ -244,7 +244,7 @@ } /** - * Checks if the specified expression returns a deterministic numeric value. + * Checks if the specified expression returns an empty sequence or a deterministic numeric value. * @param expr expression * @return result of check */ @@ -256,7 +256,7 @@ @Override public boolean has(final Flag flag) { for(final Expr pred : preds) { - if(flag == Flag.FCS && pred.seqType().mayBeNumber() || pred.has(flag)) return true; + if(flag == Flag.POS && pred.seqType().mayBeNumber() || pred.has(flag)) return true; } return false; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/RangeAccess.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/RangeAccess.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/RangeAccess.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/RangeAccess.java 2015-07-14 10:54:40.000000000 +0000 @@ -37,10 +37,10 @@ } @Override - public AxisIter iter(final QueryContext qc) { + public BasicNodeIter iter(final QueryContext qc) { final byte kind = index.type() == IndexType.TEXT ? Data.TEXT : Data.ATTR; - return new AxisIter() { + return new BasicNodeIter() { final IndexIterator it = ictx.data.iter(index); @Override public ANode next() { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Root.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Root.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Root.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Root.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,7 +6,7 @@ import org.basex.query.*; import org.basex.query.iter.*; import org.basex.query.util.*; -import org.basex.query.value.*; +import org.basex.query.util.list.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.query.value.type.*; @@ -36,15 +36,15 @@ } @Override - public Iter iter(final QueryContext qc) throws QueryException { + public BasicNodeIter iter(final QueryContext qc) throws QueryException { final Iter iter = ctxValue(qc).iter(); - final NodeSeqBuilder nc = new NodeSeqBuilder().check(); - for(Item i; (i = iter.next()) != null;) { - final ANode n = root(i); + final ANodeList list = new ANodeList().check(); + for(Item it; (it = iter.next()) != null;) { + final ANode n = it instanceof ANode ? ((ANode) it).root() : null; if(n == null || n.type != NodeType.DOC) throw CTXNODE.get(info); - nc.add(n); + list.add(n); } - return nc; + return list.iter(); } @Override @@ -52,21 +52,6 @@ return new Root(info); } - /** - * Returns the root node of the specified item. - * @param v input node - * @return root node - */ - public static ANode root(final Value v) { - if(!(v instanceof ANode)) return null; - ANode n = (ANode) v; - while(true) { - final ANode p = n.parent(); - if(p == null) return n; - n = p; - } - } - @Override public boolean has(final Flag flag) { return flag == Flag.CTX; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Set.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Set.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Set.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Set.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,6 +6,7 @@ import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.node.*; import org.basex.query.value.type.*; import org.basex.query.var.*; @@ -55,16 +56,16 @@ final int el = exprs.length; final Iter[] iter = new Iter[el]; for(int e = 0; e < el; e++) iter[e] = qc.iter(exprs[e]); - return iterable ? iter(iter) : eval(iter).sort(); + return iterable ? iter(iter) : eval(iter).iter(); } /** * Evaluates the specified iterators. * @param iter iterators - * @return resulting iterator + * @return resulting node list * @throws QueryException query exception */ - protected abstract NodeSeqBuilder eval(final Iter[] iter) throws QueryException; + protected abstract ANodeList eval(final Iter[] iter) throws QueryException; /** * Evaluates the specified iterators in an iterative manner. @@ -94,10 +95,10 @@ /** * Constructor. - * @param ir iterator + * @param iter iterator */ - SetIter(final Iter[] ir) { - iter = ir; + SetIter(final Iter[] iter) { + this.iter = iter; } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/SimpleMap.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/SimpleMap.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/SimpleMap.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/SimpleMap.java 2015-07-14 10:54:40.000000000 +0000 @@ -33,7 +33,7 @@ */ public static SimpleMap get(final InputInfo info, final Expr... exprs) { for(final Expr expr : exprs) { - if(expr.has(Flag.FCS)) return new CachedMap(info, exprs); + if(expr.has(Flag.POS)) return new CachedMap(info, exprs); } return new IterMap(info, exprs); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/StringRangeAccess.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/StringRangeAccess.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/StringRangeAccess.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/StringRangeAccess.java 2015-07-14 10:54:40.000000000 +0000 @@ -36,14 +36,14 @@ } @Override - public AxisIter iter(final QueryContext qc) { + public BasicNodeIter iter(final QueryContext qc) { final byte kind = index.text ? Data.TEXT : Data.ATTR; final Data data = ictx.data; final int ml = data.meta.maxlen; final IndexIterator ii = index.min.length <= ml && index.max.length <= ml && (index.text ? data.meta.textindex : data.meta.attrindex) ? data.iter(index) : scan(); - return new AxisIter() { + return new BasicNodeIter() { @Override public ANode next() { return ii.more() ? new DBNode(data, ii.pre(), kind) : null; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Treat.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Treat.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Treat.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Treat.java 2015-07-14 10:54:40.000000000 +0000 @@ -59,7 +59,7 @@ if(seqType.zeroOrOne()) { final Item n = iter.next(); if(n != null) { - final ValueBuilder vb = new ValueBuilder(3).add(it).add(n); + final ValueBuilder vb = new ValueBuilder().add(it).add(n); if(iter.next() != null) vb.add(Str.get(DOTS)); throw NOTREAT_X_X_X.get(info, expr.seqType(), seqType, vb.value()); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/TypeCheck.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/TypeCheck.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/TypeCheck.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/TypeCheck.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,6 +4,7 @@ import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -72,7 +73,7 @@ // check at each call if(argType.type.instanceOf(seqType.type) && !expr.has(Flag.NDT) && !expr.has(Flag.UPD)) { final Occ occ = argType.occ.intersect(seqType.occ); - if(occ == null) throw INVCAST_X_X.get(info, argType, seqType); + if(occ == null) throw INVPROMOTE_X_X.get(info, argType, seqType); } final Expr opt = expr.typeCheck(this, qc, scp); @@ -87,7 +88,7 @@ return new Iter() { /** Item cache. */ - final ValueBuilder vb = new ValueBuilder(); + final ItemList cache = new ItemList(); /** Item cache index. */ int c; /** Result index. */ @@ -96,21 +97,21 @@ @Override public Item next() throws QueryException { final SeqType st = seqType; - while(c == vb.size()) { + while(c == cache.size()) { qc.checkStop(); - vb.size(0); + cache.size(0); c = 0; final Item it = iter.next(); if(it == null || st.instance(it, true)) { - vb.add(it); + cache.add(it); } else { - st.promote(qc, sc, info, it, false, vb); + st.promote(qc, sc, info, it, false, cache); } } - final Item it = vb.get(c); - vb.set(c++, null); + final Item it = cache.get(c); + cache.set(c++, null); if(it == null && i < st.occ.min || i > st.occ.max) throw INVPROMOTE_X_X.get(info, expr.seqType(), st); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Union.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Union.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/Union.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/Union.java 2015-07-14 10:54:40.000000000 +0000 @@ -58,12 +58,12 @@ } @Override - protected NodeSeqBuilder eval(final Iter[] iter) throws QueryException { - final NodeSeqBuilder nc = new NodeSeqBuilder().check(); + protected ANodeList eval(final Iter[] iter) throws QueryException { + final ANodeList list = new ANodeList().check(); for(final Iter ir : iter) { - for(Item it; (it = ir.next()) != null;) nc.add(toNode(it)); + for(Item it; (it = ir.next()) != null;) list.add(toNode(it)); } - return nc; + return list; } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ValueAccess.java basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ValueAccess.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/expr/ValueAccess.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/expr/ValueAccess.java 2015-07-14 10:54:40.000000000 +0000 @@ -50,13 +50,13 @@ } @Override - public NodeIter iter(final QueryContext qc) throws QueryException { - final ArrayList iter = new ArrayList<>(); + public BasicNodeIter iter(final QueryContext qc) throws QueryException { + final ArrayList iter = new ArrayList<>(); final Iter ir = qc.iter(expr); for(Item it; (it = ir.next()) != null;) iter.add(index(it.string(info))); final int is = iter.size(); - return is == 0 ? AxisMoreIter.EMPTY : is == 1 ? iter.get(0) : - new Union(info, expr).eval(iter.toArray(new NodeIter[is])); + return is == 0 ? BasicNodeIter.EMPTY : is == 1 ? iter.get(0) : + new Union(info, expr).eval(iter.toArray(new NodeIter[is])).iter(); } /** @@ -64,12 +64,12 @@ * @param term term to be found * @return iterator */ - private AxisIter index(final byte[] term) { + private BasicNodeIter index(final byte[] term) { // special case: empty text node // - no element name: return 0 results (empty text nodes are non-existent) // - otherwise, return scan-based element iterator final int tl = term.length; - if(tl == 0 && text) return test == null ? AxisMoreIter.EMPTY : scanEmpty(); + if(tl == 0 && text) return test == null ? BasicNodeIter.EMPTY : scanEmpty(); // use index traversal if index exists and if term is not too long. // otherwise, scan data sequentially @@ -79,14 +79,14 @@ final int kind = text ? Data.TEXT : Data.ATTR; final DBNode tmp = new DBNode(data, 0, test == null ? kind : Data.ELEM); - return new AxisIter() { + return new BasicNodeIter() { @Override public ANode next() { while(ii.more()) { if(test == null) { - tmp.pre = ii.pre(); + tmp.pre(ii.pre()); } else { - tmp.pre = data.parent(ii.pre(), kind); + tmp.pre(data.parent(ii.pre(), kind)); if(!test.eq(tmp)) continue; } return tmp.finish(); @@ -132,8 +132,8 @@ * b) having no descendants. * @return node iterator */ - private AxisIter scanEmpty() { - return new AxisIter() { + private BasicNodeIter scanEmpty() { + return new BasicNodeIter() { final Data data = ictx.data; final DBNode tmp = new DBNode(data, 0, Data.ELEM); final int sz = data.meta.size; @@ -143,7 +143,7 @@ public DBNode next() { while(++pre < sz) { if(data.kind(pre) == Data.ELEM && data.size(pre, Data.ELEM) == 1) { - tmp.pre = pre; + tmp.pre(pre); if(test == null || test.eq(tmp)) return tmp.finish(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/admin/AdminDeleteLogs.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/admin/AdminDeleteLogs.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/admin/AdminDeleteLogs.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/admin/AdminDeleteLogs.java 2015-07-14 10:54:40.000000000 +0000 @@ -23,11 +23,11 @@ final String date = Token.string(toToken(exprs[0], qc)); final String cdate = Log.name(new Date()); - if(date.equals(cdate)) throw BXCA_TODAY.get(info, date + IO.LOGSUFFIX); + if(date.equals(cdate)) throw BXAD_TODAY.get(info, date + IO.LOGSUFFIX); final IOFile file = new IOFile(qc.context.log.dir(), date + IO.LOGSUFFIX); if(!file.exists()) throw WHICHRES_X.get(info, file); - if(!file.delete()) throw BXCA_DELETE_X.get(info, date + IO.LOGSUFFIX); + if(!file.delete()) throw BXAD_DELETE_X.get(info, date + IO.LOGSUFFIX); return null; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/admin/AdminLogs.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/admin/AdminLogs.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/admin/AdminLogs.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/admin/AdminLogs.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,6 +11,7 @@ import org.basex.io.in.*; import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.value.*; import org.basex.query.value.node.*; import org.basex.server.*; import org.basex.server.Log.LogEntry; @@ -26,6 +27,11 @@ public final class AdminLogs extends AdminFn { @Override public Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); + } + + @Override + public Value value(final QueryContext qc) throws QueryException { checkAdmin(qc); final ValueBuilder vb = new ValueBuilder(); @@ -72,7 +78,7 @@ vb.add(elem); } } - return vb; + return vb.value(); } /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/admin/AdminSessions.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/admin/AdminSessions.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/admin/AdminSessions.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/admin/AdminSessions.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,6 +5,7 @@ import org.basex.data.*; import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.value.*; import org.basex.query.value.node.*; import org.basex.server.*; @@ -17,6 +18,11 @@ public final class AdminSessions extends AdminFn { @Override public Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); + } + + @Override + public Value value(final QueryContext qc) throws QueryException { checkAdmin(qc); final ValueBuilder vb = new ValueBuilder(); @@ -30,6 +36,6 @@ vb.add(elem); } } - return vb; + return vb.value(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/admin/AdminWriteLog.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/admin/AdminWriteLog.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/admin/AdminWriteLog.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/admin/AdminWriteLog.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,5 +1,8 @@ package org.basex.query.func.admin; +import static org.basex.query.QueryError.*; +import static org.basex.util.Token.*; + import org.basex.core.users.*; import org.basex.query.*; import org.basex.query.value.item.*; @@ -18,11 +21,14 @@ public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { checkAdmin(qc); - final String msg = Token.string(toToken(exprs[0], qc)); + final String msg = string(toToken(exprs[0], qc)); + final String type = exprs.length > 1 ? string(toToken(exprs[1], qc)) : LogType.INFO.toString(); + if(!type.matches("^[A-Z]+$")) throw BXAD_TYPE_X.get(info, type); + final ClientListener cl = qc.context.listener; final String addr = cl == null ? Log.SERVER : cl.address(); final User user = (cl == null ? qc.context : cl.context()).user(); - qc.context.log.write(addr, user, LogType.INFO, msg, null); + qc.context.log.write(addr, user, type, msg, null); return null; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveCreateFrom.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveCreateFrom.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveCreateFrom.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveCreateFrom.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,62 @@ +package org.basex.query.func.archive; + +import static org.basex.query.QueryError.*; +import static org.basex.query.func.archive.ArchiveText.*; +import static org.basex.util.Token.*; + +import java.io.*; +import java.util.*; + +import org.basex.io.*; +import org.basex.query.*; +import org.basex.query.iter.*; +import org.basex.query.value.item.*; +import org.basex.query.value.seq.*; +import org.basex.util.*; +import org.basex.util.list.*; + +/** + * Function implementation. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public class ArchiveCreateFrom extends ArchiveCreate { + @Override + public B64 item(final QueryContext qc, final InputInfo ii) throws QueryException { + checkCreate(qc); + + final IOFile root = new IOFile(toPath(0, qc).toString()); + final ArchOptions opts = toOptions(1, Q_OPTIONS, new ArchOptions(), qc); + final Iter entries; + if(exprs.length > 2) { + entries = qc.iter(exprs[2]); + } else { + final TokenList tl = new TokenList(); + for(final String file : root.descendants()) tl.add(file); + entries = StrSeq.get(tl).iter(); + } + + final String format = opts.get(ArchOptions.FORMAT); + final int level = level(opts); + if(!root.isDir()) throw FILE_NO_DIR_X.get(info, root); + + try(final ArchiveOut out = ArchiveOut.get(format.toLowerCase(Locale.ENGLISH), info)) { + out.level(level); + try { + while(true) { + Item en = entries.next(); + if(en == null) break; + en = checkElemToken(en); + final IOFile file = new IOFile(root, string(en.string(info))); + if(!file.exists()) throw FILE_NOT_FOUND_X.get(info, file); + if(file.isDir()) throw FILE_IS_DIR_X.get(info, file); + add(en, new B64(file.read()), out, level, qc); + } + } catch(final IOException ex) { + throw ARCH_FAIL_X.get(info, ex); + } + return new B64(out.finish()); + } + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveCreate.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveCreate.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveCreate.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveCreate.java 2015-07-14 10:54:40.000000000 +0000 @@ -14,7 +14,6 @@ import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.util.*; -import org.basex.util.options.*; /** * Function implementation. @@ -25,41 +24,32 @@ public class ArchiveCreate extends ArchiveFn { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final Iter entr = qc.iter(exprs[0]), cont = qc.iter(exprs[1]); - final Options opts = toOptions(2, Q_OPTIONS, new ArchOptions(), qc); + final Iter entries = qc.iter(exprs[0]), contents = qc.iter(exprs[1]); + final ArchOptions opts = toOptions(2, Q_OPTIONS, new ArchOptions(), qc); + // check options final String format = opts.get(ArchOptions.FORMAT); + final int level = level(opts); + try(final ArchiveOut out = ArchiveOut.get(format.toLowerCase(Locale.ENGLISH), info)) { - // check algorithm - final String alg = opts.get(ArchOptions.ALGORITHM); - int level = ZipEntry.DEFLATED; - if(alg != null) { - if(format.equals(ZIP) && !Strings.eq(alg, STORED, DEFLATE) || - format.equals(GZIP) && !Strings.eq(alg, DEFLATE)) { - throw ARCH_SUPP_X_X.get(info, ArchOptions.ALGORITHM.name(), alg); - } - if(Strings.eq(alg, STORED)) level = ZipEntry.STORED; - else if(Strings.eq(alg, DEFLATE)) level = ZipEntry.DEFLATED; - } out.level(level); - try { - Item en, cn; int e = 0, c = 0; while(true) { - en = entr.next(); - cn = cont.next(); - if(en == null || cn == null) break; + final Item en = entries.next(), cn = contents.next(); + if(en == null || cn == null) { + // count remaining entries + if(cn != null) do c++; while(contents.next() != null); + if(en != null) do e++; while(entries.next() != null); + if(e != c) throw ARCH_DIFF_X_X.get(info, e, c); + break; + } if(out instanceof GZIPOut && c > 0) throw ARCH_ONE_X.get(info, format.toUpperCase(Locale.ENGLISH)); add(checkElemToken(en), cn, out, level, qc); e++; c++; } - // count remaining entries - if(cn != null) do c++; while(cont.next() != null); - if(en != null) do e++; while(entr.next() != null); - if(e != c) throw ARCH_DIFF_X_X.get(info, e, c); } catch(final IOException ex) { throw ARCH_FAIL_X.get(info, ex); } @@ -68,6 +58,27 @@ } /** + * Returns the compression level. + * @param options options + * @return level + * @throws QueryException query exception + */ + protected int level(final ArchOptions options) throws QueryException { + int level = ZipEntry.DEFLATED; + final String format = options.get(ArchOptions.FORMAT); + final String alg = options.get(ArchOptions.ALGORITHM); + if(alg != null) { + if(format.equals(ZIP) && !Strings.eq(alg, STORED, DEFLATE) || + format.equals(GZIP) && !Strings.eq(alg, DEFLATE)) { + throw ARCH_SUPP_X_X.get(info, ArchOptions.ALGORITHM.name(), alg); + } + if(Strings.eq(alg, STORED)) level = ZipEntry.STORED; + else if(Strings.eq(alg, DEFLATE)) level = ZipEntry.DEFLATED; + } + return level; + } + + /** * Adds the specified entry to the output stream. * @param entry entry descriptor * @param cont contents @@ -77,7 +88,7 @@ * @throws QueryException query exception * @throws IOException I/O exception */ - final void add(final Item entry, final Item cont, final ArchiveOut out, final int level, + protected final void add(final Item entry, final Item cont, final ArchiveOut out, final int level, final QueryContext qc) throws QueryException, IOException { // create new zip entry diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveEntries.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveEntries.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveEntries.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveEntries.java 2015-07-14 10:54:40.000000000 +0000 @@ -10,6 +10,7 @@ import org.basex.query.*; import org.basex.query.func.*; import org.basex.query.iter.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -22,6 +23,11 @@ public final class ArchiveEntries extends StandardFunc { @Override public Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); + } + + @Override + public Value value(final QueryContext qc) throws QueryException { final B64 archive = toB64(exprs[0], qc, false); final ValueBuilder vb = new ValueBuilder(); try(final ArchiveIn in = ArchiveIn.get(archive.input(info), info)) { @@ -38,7 +44,7 @@ e.add(ze.getName()); vb.add(e); } - return vb; + return vb.value(); } catch(final IOException ex) { throw ARCH_FAIL_X.get(info, ex); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveExtractBinary.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveExtractBinary.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveExtractBinary.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveExtractBinary.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,6 +8,7 @@ import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.util.hash.*; import org.basex.util.list.*; @@ -21,9 +22,14 @@ public class ArchiveExtractBinary extends ArchiveFn { @Override public Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); + } + + @Override + public Value value(final QueryContext qc) throws QueryException { final ValueBuilder vb = new ValueBuilder(); for(final byte[] b : extract(qc)) vb.add(new B64(b)); - return vb; + return vb.value(); } /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveExtractText.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveExtractText.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveExtractText.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveExtractText.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,6 +4,7 @@ import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; /** @@ -15,9 +16,14 @@ public final class ArchiveExtractText extends ArchiveExtractBinary { @Override public Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); + } + + @Override + public Value value(final QueryContext qc) throws QueryException { final String enc = toEncoding(2, ARCH_ENCODING_X, qc); final ValueBuilder vb = new ValueBuilder(); for(final byte[] b : extract(qc)) vb.add(Str.get(encode(b, enc, qc))); - return vb; + return vb.value(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveExtractTo.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveExtractTo.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveExtractTo.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveExtractTo.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,49 @@ +package org.basex.query.func.archive; + +import static org.basex.query.QueryError.*; +import static org.basex.util.Token.*; + +import java.io.*; +import java.nio.file.*; +import java.util.zip.*; + +import org.basex.query.*; +import org.basex.query.value.item.*; +import org.basex.util.*; +import org.basex.util.hash.*; + +/** + * Function implementation. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public class ArchiveExtractTo extends ArchiveFn { + @Override + public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { + checkCreate(qc); + + final Path path = toPath(0, qc); + final B64 archive = toB64(exprs[1], qc, false); + final TokenSet hs = entries(2, qc); + + try(final ArchiveIn in = ArchiveIn.get(archive.input(info), info)) { + while(in.more()) { + final ZipEntry ze = in.entry(); + final String name = ze.getName(); + if(hs == null || hs.delete(token(name)) != 0) { + final Path file = path.resolve(name); + if(ze.isDirectory()) { + Files.createDirectories(file); + } else { + Files.createDirectories(file.getParent()); + Files.write(file, in.read()); + } + } + } + } catch(final IOException ex) { + throw ARCH_FAIL_X.get(info, ex); + } + return null; + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveWrite.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveWrite.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/archive/ArchiveWrite.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/archive/ArchiveWrite.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,16 +1,5 @@ package org.basex.query.func.archive; -import static org.basex.query.QueryError.*; -import static org.basex.util.Token.*; - -import java.io.*; -import java.nio.file.*; -import java.util.zip.*; - -import org.basex.query.*; -import org.basex.query.value.item.*; -import org.basex.util.*; -import org.basex.util.hash.*; /** * Function implementation. @@ -18,32 +7,5 @@ * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ -public final class ArchiveWrite extends ArchiveFn { - @Override - public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - checkCreate(qc); - - final Path path = toPath(0, qc); - final B64 archive = toB64(exprs[1], qc, false); - final TokenSet hs = entries(2, qc); - - try(final ArchiveIn in = ArchiveIn.get(archive.input(info), info)) { - while(in.more()) { - final ZipEntry ze = in.entry(); - final String name = ze.getName(); - if(hs == null || hs.delete(token(name)) != 0) { - final Path file = path.resolve(name); - if(ze.isDirectory()) { - Files.createDirectories(file); - } else { - Files.createDirectories(file.getParent()); - Files.write(file, in.read()); - } - } - } - } catch(final IOException ex) { - throw ARCH_FAIL_X.get(info, ex); - } - return null; - } +public final class ArchiveWrite extends ArchiveExtractTo { } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayFilter.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayFilter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayFilter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayFilter.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,5 @@ package org.basex.query.func.array; -import java.util.*; - import org.basex.query.*; import org.basex.query.value.*; import org.basex.query.value.array.*; @@ -21,10 +19,8 @@ final Array array = toArray(exprs[0], qc); final FItem fun = checkArity(exprs[1], 1, qc); final ArrayBuilder builder = new ArrayBuilder(); - final Iterator iter = array.members(); - while(iter.hasNext()) { - final Value v = iter.next(); - if(toBoolean(fun.invokeItem(qc, info, v))) builder.append(v); + for(final Value val : array.members()) { + if(toBoolean(fun.invokeItem(qc, info, val))) builder.append(val); } return builder.freeze(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayFlatten.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayFlatten.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayFlatten.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayFlatten.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,11 @@ package org.basex.query.func.array; +import java.util.*; + import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.value.*; +import org.basex.query.value.array.*; import org.basex.query.value.item.*; /** @@ -9,13 +13,69 @@ * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen + * @author Leo Woerteler */ public final class ArrayFlatten extends ArrayFn { @Override - public Iter iter(final QueryContext qc) throws QueryException { + public Value value(final QueryContext qc) throws QueryException { final ValueBuilder vb = new ValueBuilder(); - final Iter ir = qc.iter(exprs[0]); - for(Item it; (it = ir.next()) != null;) vb.addFlattened(it); - return vb; + final Iter iter = qc.iter(exprs[0]); + for(Item it; (it = iter.next()) != null;) { + if(it instanceof Array) addFlattened(vb, (Array) it); + else vb.add(it); + } + return vb.value(); + } + + /** + * Recursive helper method for flattening nested arrays. + * @param vb sequence builder + * @param arr current array + */ + private static void addFlattened(final ValueBuilder vb, final Array arr) { + for(final Value val : arr.members()) { + for(final Item it : val) { + if(it instanceof Array) addFlattened(vb, (Array) it); + else vb.add(it); + } + } + } + + @Override + public Iter iter(final QueryContext qc) throws QueryException { + return new Iter() { + @SuppressWarnings("unchecked") + private Iterator[] iters = new Iterator[2]; + private int p = -1; + private Iter curr = qc.iter(exprs[0]); + + @Override + public Item next() throws QueryException { + for(;;) { + final Item it = curr.next(); + + if(it != null) { + if(!(it instanceof Array)) return it; + final Array arr = (Array) it; + if(++p == iters.length) { + @SuppressWarnings("unchecked") + final Iterator[] temp = new Iterator[2 * p]; + System.arraycopy(iters, 0, temp, 0, p); + iters = temp; + } + iters[p] = arr.iterator(0); + } else if(p < 0) { + return null; + } + + while(!iters[p].hasNext()) { + iters[p] = null; + if(--p < 0) return null; + } + + curr = iters[p].next().iter(); + } + } + }; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayFoldLeft.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayFoldLeft.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayFoldLeft.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayFoldLeft.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,5 @@ package org.basex.query.func.array; -import java.util.*; - import org.basex.query.*; import org.basex.query.iter.*; import org.basex.query.value.*; @@ -25,8 +23,7 @@ final Array array = toArray(exprs[0], qc); Value res = qc.value(exprs[1]); final FItem fun = checkArity(exprs[2], 2, qc); - final Iterator iter = array.members(); - while(iter.hasNext()) res = fun.invokeValue(qc, info, res, iter.next()); + for(final Value val : array.members()) res = fun.invokeValue(qc, info, res, val); return res; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayFoldRight.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayFoldRight.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayFoldRight.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayFoldRight.java 2015-07-14 10:54:40.000000000 +0000 @@ -25,7 +25,7 @@ final Array array = toArray(exprs[0], qc); Value res = qc.value(exprs[1]); final FItem fun = checkArity(exprs[2], 2, qc); - final ListIterator iter = array.members(true); + final ListIterator iter = array.iterator(array.arraySize()); while(iter.hasPrevious()) res = fun.invokeValue(qc, info, iter.previous(), res); return res; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayForEach.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayForEach.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayForEach.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayForEach.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,5 @@ package org.basex.query.func.array; -import java.util.*; - import org.basex.query.*; import org.basex.query.value.*; import org.basex.query.value.array.*; @@ -21,8 +19,7 @@ final Array array = toArray(exprs[0], qc); final FItem fun = checkArity(exprs[1], 1, qc); final ArrayBuilder builder = new ArrayBuilder(); - final Iterator iter = array.members(); - while(iter.hasNext()) builder.append(fun.invokeValue(qc, info, iter.next())); + for(final Value val : array.members()) builder.append(fun.invokeValue(qc, info, val)); return builder.freeze(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayForEachPair.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayForEachPair.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayForEachPair.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayForEachPair.java 2015-07-14 10:54:40.000000000 +0000 @@ -21,7 +21,7 @@ final Array array1 = toArray(exprs[0], qc), array2 = toArray(exprs[1], qc); final FItem fun = checkArity(exprs[2], 2, qc); final ArrayBuilder builder = new ArrayBuilder(); - final Iterator as = array1.members(), bs = array2.members(); + final Iterator as = array1.iterator(0), bs = array2.iterator(0); while(as.hasNext() && bs.hasNext()) builder.append(fun.invokeValue(qc, info, as.next(), bs.next())); return builder.freeze(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayReverse.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayReverse.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArrayReverse.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArrayReverse.java 2015-07-14 10:54:40.000000000 +0000 @@ -13,6 +13,6 @@ public final class ArrayReverse extends ArrayFn { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - return toArray(exprs[0], qc).reverse(); + return toArray(exprs[0], qc).reverseArray(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArraySort.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArraySort.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/array/ArraySort.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/array/ArraySort.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,5 @@ package org.basex.query.func.array; -import java.util.*; - import org.basex.query.*; import org.basex.query.func.*; import org.basex.query.func.fn.*; @@ -21,22 +19,21 @@ public final class ArraySort extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final Array value = toArray(exprs[0], qc); + final Array array = toArray(exprs[0], qc); - final int sz = (int) value.arraySize(); + final int sz = (int) array.arraySize(); final ValueList vl = new ValueList(sz); - final Iterator iter = value.members(); if(exprs.length > 1) { final FItem key = checkArity(exprs[1], 1, qc); - while(iter.hasNext()) vl.add(key.invokeValue(qc, info, iter.next())); + for(final Value val : array.members()) vl.add(key.invokeValue(qc, info, val)); } else { - while(iter.hasNext()) vl.add(iter.next()); + for(final Value val : array.members()) vl.add(val); } final Integer[] order = FnSort.sort(vl, this); final ArrayBuilder builder = new ArrayBuilder(); if(exprs.length > 1) { - for(int r = 0; r < sz; r++) builder.append(value.get(order[r])); + for(int r = 0; r < sz; r++) builder.append(array.get(order[r])); } else { for(int r = 0; r < sz; r++) builder.append(vl.get(order[r])); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/crypto/CryptoGenerateSignature.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/crypto/CryptoGenerateSignature.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/crypto/CryptoGenerateSignature.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/crypto/CryptoGenerateSignature.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,7 +6,6 @@ import org.basex.query.func.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; -import org.basex.query.value.type.*; import org.basex.util.*; /** @@ -22,10 +21,13 @@ Item arg6 = null; boolean arg6Str = false; if(exprs.length > 6) { - arg6 = toItem(exprs[6], qc); - if(arg6 instanceof AStr) arg6Str = true; - else if(!(arg6 instanceof ANode)) throw castError(info, arg6, AtomType.STR); + arg6 = toNodeOrAtomItem(exprs[6], qc); + if(!(arg6 instanceof ANode)) { + if(!arg6.type.isStringOrUntyped()) throw STRNOD_X_X.get(info, arg6.type, arg6); + arg6Str = true; + } } + return new DigitalSignature(ii).generateSignature( toNode(exprs[0], qc), toToken(exprs[1], qc), toToken(exprs[2], qc), toToken(exprs[3], qc), diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbAdd.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbAdd.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbAdd.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbAdd.java 2015-07-14 10:54:40.000000000 +0000 @@ -21,7 +21,7 @@ public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { final Data data = checkData(qc); final byte[] path = exprs.length < 3 ? EMPTY : token(path(2, qc)); - final NewInput input = checkInput(toItem(exprs[1], qc), path); + final NewInput input = checkInput(toNodeOrAtomItem(exprs[1], qc), path); final Options opts = toOptions(3, Q_OPTIONS, new Options(), qc); qc.resources.updates().add(new DBAdd(data, input, opts, qc, info), qc); return null; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbBackups.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbBackups.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbBackups.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbBackups.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,12 +2,16 @@ import static org.basex.util.Token.*; +import java.util.*; + +import org.basex.core.*; import org.basex.io.*; import org.basex.query.*; import org.basex.query.func.*; import org.basex.query.iter.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; +import org.basex.util.*; import org.basex.util.list.*; /** @@ -17,10 +21,14 @@ * @author Christian Gruen */ public final class DbBackups extends StandardFunc { - /** Backup element name. */ + /** Backup string. */ private static final String BACKUP = "backup"; - /** Size element name. */ + /** Size string. */ private static final String SIZE = "size"; + /** Date string. */ + private static final String DATE = "date"; + /** Database string. */ + private static final String DATABASE = "database"; @Override public Iter iter(final QueryContext qc) throws QueryException { @@ -34,11 +42,19 @@ int up = -1; @Override - public Item next() { + public Item next() throws QueryException { if(++up >= backups.size()) return null; final String backup = backups.get(up); final long length = new IOFile(dbpath, backup + IO.ZIPSUFFIX).length(); - return new FElem(BACKUP).add(backup).add(SIZE, token(length)); + final String db = Databases.name(backup); + + final Date date = Databases.date(backup); + final String ymd = DateTime.format(date, DateTime.DATE); + final String hms = DateTime.format(date, DateTime.TIME); + final Dtm dtm = new Dtm(token(ymd + 'T' + hms), info); + + return new FElem(BACKUP).add(backup).add(DATABASE, db).add(DATE, dtm.string(info)). + add(SIZE, token(length)); } }; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbEvent.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbEvent.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbEvent.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbEvent.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -package org.basex.query.func.db; - -import static org.basex.query.QueryError.*; - -import org.basex.io.out.*; -import org.basex.io.serial.*; -import org.basex.query.*; -import org.basex.query.func.*; -import org.basex.query.value.item.*; -import org.basex.util.*; - -/** - * Function implementation. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -public final class DbEvent extends StandardFunc { - @Override - public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final byte[] name = toToken(exprs[0], qc); - try { - final ArrayOutput ao = qc.value(exprs[1]).serialize(SerializerOptions.get(false)); - // throw exception if event is unknown - if(!qc.context.events.notify(qc.context, name, ao.finish())) - throw BXDB_EVENT_X.get(info, name); - return null; - } catch(final QueryIOException ex) { - throw ex.getCause(info); - } - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbListDetails.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbListDetails.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbListDetails.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbListDetails.java 2015-07-14 10:54:40.000000000 +0000 @@ -56,8 +56,10 @@ @Override public ANode get(final long i) { if(i < is) { - final byte[] pt = data.text(il.get((int) i), true); - return resource(pt, false, 0, MediaType.APPLICATION_XML, data.meta.time); + final int pre = il.get((int) i); + final byte[] pt = data.text(pre, true); + return resource(pt, false, data.size(pre, Data.DOC), MediaType.APPLICATION_XML, + data.meta.time); } if(i < is + ts) { final byte[] pt = tl.get((int) i - is); @@ -71,7 +73,7 @@ return ip < is ? get(ip++) : tp < ts ? get(ip + tp++) : null; } @Override - public long size() { return ip + is; } + public long size() { return is + ts; } }; } @@ -88,20 +90,21 @@ @Override public ANode get(final long i) throws QueryException { final String name = dbs.get((int) i); - final MetaData meta = new MetaData(name, ctx.options, ctx.soptions); try { + final MetaData meta = new MetaData(name, ctx.options, ctx.soptions); meta.read(); + // count number of raw files + final int bin = new IOFile(ctx.soptions.dbpath(name), IO.RAW).descendants().size(); + final FElem res = new FElem(DATABASE); + res.add(RESOURCES, token(meta.ndocs + bin)); + res.add(MDATE, DateTime.format(new Date(meta.dbtime()))); + res.add(SIZE, token(meta.dbsize())); + if(ctx.perm(Perm.CREATE, name)) res.add(PATH, meta.original); + res.add(name); + return res; } catch(final IOException ex) { throw BXDB_OPEN_X.get(info, ex); } - - final FElem res = new FElem(DATABASE); - res.add(RESOURCES, token(meta.ndocs)); - res.add(MDATE, DateTime.format(new Date(meta.dbtime()), DateTime.FULL)); - res.add(SIZE, token(meta.dbsize())); - if(ctx.perm(Perm.CREATE, name)) res.add(PATH, meta.original); - res.add(name); - return res; } @Override public ANode next() throws QueryException { return pos < size() ? get(pos++) : null; } @@ -122,9 +125,8 @@ private static FNode resource(final byte[] path, final boolean raw, final long size, final MediaType type, final long mdate) { - final String tstamp = DateTime.format(new Date(mdate), DateTime.FULL); - final FElem res = new FElem(RESOURCE).add(path). - add(RAW, token(raw)).add(CTYPE, type.toString()).add(MDATE, tstamp); - return raw ? res.add(SIZE, token(size)) : res; + final String tstamp = DateTime.format(new Date(mdate)); + return new FElem(RESOURCE).add(path). + add(RAW, token(raw)).add(CTYPE, type.toString()).add(MDATE, tstamp).add(SIZE, token(size)); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbName.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbName.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbName.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbName.java 2015-07-14 10:54:40.000000000 +0000 @@ -13,6 +13,6 @@ public final class DbName extends DbAccess { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - return Str.get(toDBNode(toNode(exprs[0], qc)).data.meta.name); + return Str.get(toDBNode(toNode(exprs[0], qc)).data().meta.name); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbNew.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbNew.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbNew.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbNew.java 2015-07-14 10:54:40.000000000 +0000 @@ -24,7 +24,7 @@ /** * Creates a {@link Data} instance for the specified document. - * @param in input item + * @param in input item (node or string) * @param path optional path argument * @return database instance * @throws QueryException query exception diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbNodeId.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbNodeId.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbNodeId.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbNodeId.java 2015-07-14 10:54:40.000000000 +0000 @@ -23,7 +23,7 @@ final Item it = ir.next(); if(it == null) return null; final DBNode node = toDBNode(it); - return Int.get(node.data.id(node.pre)); + return Int.get(node.data().id(node.pre())); } }; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbNodePre.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbNodePre.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbNodePre.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbNodePre.java 2015-07-14 10:54:40.000000000 +0000 @@ -20,7 +20,7 @@ @Override public Int next() throws QueryException { final Item it = ir.next(); - return it == null ? null : Int.get(toDBNode(it).pre); + return it == null ? null : Int.get(toDBNode(it).pre()); } }; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbOutputCache.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbOutputCache.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbOutputCache.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbOutputCache.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,24 @@ +package org.basex.query.func.db; + +import org.basex.query.*; +import org.basex.query.func.*; +import org.basex.query.iter.*; +import org.basex.query.value.*; + +/** + * Function implementation. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class DbOutputCache extends StandardFunc { + @Override + public Value value(final QueryContext qc) throws QueryException { + return qc.resources.output.value(); + } + + @Override + public Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbPath.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbPath.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbPath.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbPath.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,5 +1,6 @@ package org.basex.query.func.db; +import org.basex.data.*; import org.basex.query.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -13,13 +14,13 @@ */ public final class DbPath extends DbAccess { @Override - public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { + public Str item(final QueryContext qc, final InputInfo ii) throws QueryException { ANode node, par = toNode(exprs[0], qc); do { node = par; par = node.parent(); } while(par != null); final DBNode dbn = toDBNode(node); - return Str.get(dbn.data.text(dbn.pre, true)); + return dbn.kind() == Data.DOC ? Str.get(dbn.data().text(dbn.pre(), true)) : Str.ZERO; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbReplace.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbReplace.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbReplace.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbReplace.java 2015-07-14 10:54:40.000000000 +0000 @@ -26,7 +26,7 @@ public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { final Data data = checkData(qc); final String path = path(1, qc); - final Item item = toItem(exprs[2], qc); + final Item item = toNodeOrAtomItem(exprs[2], qc); final Options opts = toOptions(3, Q_OPTIONS, new Options(), qc); final Updates updates = qc.resources.updates(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbStore.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbStore.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/db/DbStore.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/db/DbStore.java 2015-07-14 10:54:40.000000000 +0000 @@ -20,7 +20,7 @@ public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { final Data data = checkData(qc); final String path = path(1, qc); - final Item item = toItem(exprs[2], qc); + final Item item = toNodeOrAtomItem(exprs[2], qc); if(data.inMemory()) throw BXDB_MEM_X.get(info, data.meta.name); final IOFile file = data.meta.binary(path); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/file/FileCopy.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/file/FileCopy.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/file/FileCopy.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/file/FileCopy.java 2015-07-14 10:54:40.000000000 +0000 @@ -49,11 +49,7 @@ } // ignore operations on identical, canonical source and target path - if(copy) { - copy(src, trg); - } else { - Files.move(src, trg, StandardCopyOption.REPLACE_EXISTING); - } + relocate(src, trg, copy); return null; } @@ -61,16 +57,22 @@ * Recursively copies files. * @param src source path * @param trg target path + * @param copy copy flag * @throws IOException I/O exception */ - private synchronized void copy(final Path src, final Path trg) throws IOException { + private synchronized void relocate(final Path src, final Path trg, final boolean copy) + throws IOException { + if(Files.isDirectory(src)) { Files.createDirectory(trg); try(DirectoryStream paths = Files.newDirectoryStream(src)) { - for(final Path p : paths) copy(p, trg.resolve(p.getFileName())); + for(final Path p : paths) relocate(p, trg.resolve(p.getFileName()), copy); } - } else { + if(!copy) Files.delete(src); + } else if(copy) { Files.copy(src, trg, StandardCopyOption.REPLACE_EXISTING); + } else { + Files.move(src, trg, StandardCopyOption.REPLACE_EXISTING); } } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/file/FileIsAbsolute.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/file/FileIsAbsolute.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/file/FileIsAbsolute.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/file/FileIsAbsolute.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,17 @@ +package org.basex.query.func.file; + +import org.basex.query.*; +import org.basex.query.value.item.*; + +/** + * Function implementation. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class FileIsAbsolute extends FileFn { + @Override + public Item item(final QueryContext qc) throws QueryException { + return Bln.get(toPath(0, qc).isAbsolute()); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/file/FileResolvePath.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/file/FileResolvePath.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/file/FileResolvePath.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/file/FileResolvePath.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,5 +1,7 @@ package org.basex.query.func.file; +import static org.basex.query.QueryError.*; + import java.nio.file.*; import org.basex.query.*; @@ -14,7 +16,15 @@ public final class FileResolvePath extends FileFn { @Override public Item item(final QueryContext qc) throws QueryException { - final Path abs = absolute(toPath(0, qc)); + final Path path = toPath(0, qc); + final Path abs; + if(exprs.length < 2) { + abs = absolute(path); + } else { + final Path base = toPath(1, qc); + if(!base.isAbsolute()) throw FILE_IS_RELATIVE_X.get(info, base); + abs = base.resolve(path); + } return get(abs, Files.isDirectory(abs)); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/Compare.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/Compare.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/Compare.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/Compare.java 2015-07-14 10:54:40.000000000 +0000 @@ -117,9 +117,9 @@ ANode s1 = (ANode) it1, s2 = (ANode) it2; if(s1.is(s2)) continue; - AxisIter ch1 = s1.children(), ch2 = s2.children(); + BasicNodeIter ch1 = s1.children(), ch2 = s2.children(); - final Stack stack = new Stack<>(); + final Stack stack = new Stack<>(); stack.push(ch1); stack.push(ch2); @@ -163,12 +163,12 @@ if(s1.attributes().value().size() != s2.attributes().value().size()) return false; // compare names, values and prefixes - final AxisIter ai1 = s1.attributes(); + final BasicNodeIter ir1 = s1.attributes(); LOOP: - for(ANode a1; (a1 = ai1.next()) != null;) { + for(ANode a1; (a1 = ir1.next()) != null;) { n1 = a1.qname(); - final AxisIter ai2 = s2.attributes(); - for(ANode a2; (a2 = ai2.next()) != null;) { + final BasicNodeIter ir2 = s2.attributes(); + for(ANode a2; (a2 = ir2.next()) != null;) { n2 = a2.qname(); if(!n1.eq(n2)) continue; if(flags.contains(Mode.NAMESPACES) && !eq(n1.prefix(), n2.prefix()) || diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnApply.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnApply.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnApply.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnApply.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,8 +2,6 @@ import static org.basex.query.QueryError.*; -import java.util.*; - import org.basex.query.*; import org.basex.query.func.*; import org.basex.query.iter.*; @@ -32,8 +30,7 @@ if(fun.arity() != as) throw APPLY_X_X.get(info, fun.arity(), as); final ValueList vl = new ValueList((int) as); - final Iterator iter = array.members(); - while(iter.hasNext()) vl.add(iter.next()); + for(final Value val : array.members()) vl.add(val); return fun.invokeValue(qc, info, vl.finish()); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnAvailableEnvironmentVariables.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnAvailableEnvironmentVariables.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnAvailableEnvironmentVariables.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnAvailableEnvironmentVariables.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,6 +3,7 @@ import org.basex.query.*; import org.basex.query.func.*; import org.basex.query.iter.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; /** @@ -13,9 +14,14 @@ */ public final class FnAvailableEnvironmentVariables extends StandardFunc { @Override - public Iter iter(final QueryContext qc) { + public Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); + } + + @Override + public Value value(final QueryContext qc) { final ValueBuilder vb = new ValueBuilder(); for(final Object k : System.getenv().keySet()) vb.add(Str.get(k.toString())); - return vb; + return vb.value(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnBaseUri.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnBaseUri.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnBaseUri.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnBaseUri.java 2015-07-14 10:54:40.000000000 +0000 @@ -20,7 +20,7 @@ public final class FnBaseUri extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final ANode node = toEmptyNode(arg(0, qc), qc); + final ANode node = toEmptyNode(ctxArg(0, qc), qc); if(node == null) return null; if(node.type != NodeType.ELM && node.type != NodeType.DOC && node.parent() == null) return null; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnCurrentDateTime.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnCurrentDateTime.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnCurrentDateTime.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnCurrentDateTime.java 2015-07-14 10:54:40.000000000 +0000 @@ -14,6 +14,6 @@ public final class FnCurrentDateTime extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - return qc.initDateTime().dtm; + return qc.initDateTime().datm; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnData.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnData.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnData.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnData.java 2015-07-14 10:54:40.000000000 +0000 @@ -19,23 +19,21 @@ public final class FnData extends StandardFunc { @Override public Iter iter(final QueryContext qc) throws QueryException { - return arg(0, qc).atomIter(qc, info); + return ctxArg(0, qc).atomIter(qc, info); } @Override public Value value(final QueryContext qc) throws QueryException { - return arg(0, qc).atomValue(qc, info); + return ctxArg(0, qc).atomValue(qc, info); } @Override protected Expr opt(final QueryContext qc, final VarScope scp) { - if(exprs.length == 1) { - final SeqType st = exprs[0].seqType(); - if(st.type instanceof NodeType) { - seqType = SeqType.get(AtomType.ATM, st.occ); - } else if(!st.mayBeArray()) { - seqType = st; - } + final SeqType st = (exprs.length > 0 ? exprs[0] : qc.value != null ? qc.value : this).seqType(); + if(st.type instanceof NodeType) { + seqType = SeqType.get(AtomType.ATM, st.occ); + } else if(!st.mayBeArray()) { + seqType = st; } return this; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnDistinctValues.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnDistinctValues.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnDistinctValues.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnDistinctValues.java 2015-07-14 10:54:40.000000000 +0000 @@ -97,7 +97,7 @@ for(final byte[] c : pn.stats.cats) is.put(new Atm(c), info); } // return resulting sequence - final ValueBuilder vb = new ValueBuilder(is.size()); + final ValueBuilder vb = new ValueBuilder(); for(final Item i : is) vb.add(i); return vb.value(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnDocumentUri.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnDocumentUri.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnDocumentUri.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnDocumentUri.java 2015-07-14 10:54:40.000000000 +0000 @@ -18,7 +18,7 @@ public final class FnDocumentUri extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final ANode node = toEmptyNode(arg(0, qc), qc); + final ANode node = toEmptyNode(ctxArg(0, qc), qc); if(node == null || node.type != NodeType.DOC) return null; final byte[] uri = node.baseURI(); return uri.length == 0 ? null : Uri.uri(uri, false); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnElementWithId.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnElementWithId.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnElementWithId.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnElementWithId.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,9 +11,7 @@ */ public final class FnElementWithId extends Ids { @Override - public Iter iter(final QueryContext qc) throws QueryException { - final NodeSeqBuilder nc = new NodeSeqBuilder().check(); - add(ids(exprs[0].atomIter(qc, info)), nc, checkRoot(toNode(arg(1, qc), qc)), false); - return nc; + public BasicNodeIter iter(final QueryContext qc) throws QueryException { + return ids(qc, false); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnFoldLeft.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnFoldLeft.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnFoldLeft.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnFoldLeft.java 2015-07-14 10:54:40.000000000 +0000 @@ -16,6 +16,15 @@ */ public final class FnFoldLeft extends StandardFunc { @Override + public Value value(final QueryContext qc) throws QueryException { + final Iter ir = exprs[0].iter(qc); + final FItem fun = checkArity(exprs[2], 2, qc); + Value res = qc.value(exprs[1]); + for(Item it; (it = ir.next()) != null;) res = fun.invokeValue(qc, info, res, it); + return res; + } + + @Override public Iter iter(final QueryContext qc) throws QueryException { final Iter ir = exprs[0].iter(qc); final FItem fun = checkArity(exprs[2], 2, qc); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnFoldRight.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnFoldRight.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnFoldRight.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnFoldRight.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,11 +1,14 @@ package org.basex.query.func.fn; +import java.util.*; + import org.basex.query.*; import org.basex.query.expr.*; import org.basex.query.func.*; import org.basex.query.iter.*; import org.basex.query.value.*; import org.basex.query.value.item.*; +import org.basex.query.value.seq.tree.*; import org.basex.query.var.*; /** @@ -16,6 +19,20 @@ */ public final class FnFoldRight extends StandardFunc { @Override + public Value value(final QueryContext qc) throws QueryException { + final Value v = qc.value(exprs[0]); + Value res = qc.value(exprs[1]); + final FItem fun = checkArity(exprs[2], 2, qc); + if(v instanceof TreeSeq) { + final ListIterator iter = ((TreeSeq) v).iterator(v.size()); + while(iter.hasPrevious()) res = fun.invokeValue(qc, info, iter.previous(), res); + } else { + for(long i = v.size(); --i >= 0;) res = fun.invokeValue(qc, info, v.itemAt(i), res); + } + return res; + } + + @Override public Iter iter(final QueryContext qc) throws QueryException { final Value v = qc.value(exprs[0]); final FItem fun = checkArity(exprs[2], 2, qc); @@ -24,7 +41,12 @@ if(v.isEmpty()) return exprs[1].iter(qc); Value res = qc.value(exprs[1]); - for(long i = v.size(); --i >= 0;) res = fun.invokeValue(qc, info, v.itemAt(i), res); + if(v instanceof TreeSeq) { + final ListIterator iter = ((TreeSeq) v).iterator(v.size()); + while(iter.hasPrevious()) res = fun.invokeValue(qc, info, iter.previous(), res); + } else { + for(long i = v.size(); --i >= 0;) res = fun.invokeValue(qc, info, v.itemAt(i), res); + } return res.iter(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnForEach.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnForEach.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnForEach.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnForEach.java 2015-07-14 10:54:40.000000000 +0000 @@ -40,6 +40,23 @@ } @Override + public Value value(final QueryContext qc) throws QueryException { + final FItem f = checkArity(exprs[1], 1, qc); + final Iter iter = exprs[0].iter(qc); + Item it = iter.next(); + if(it == null) return Empty.SEQ; + final Value v1 = f.invokeValue(qc, info, it); + it = iter.next(); + if(it == null) return v1; + + final ValueBuilder vb = new ValueBuilder().add(v1); + do { + vb.add(f.invokeValue(qc, info, it)); + } while((it = iter.next()) != null); + return vb.value(); + } + + @Override protected Expr opt(final QueryContext qc, final VarScope scp) throws QueryException { if(allAreValues() && exprs[0].size() < UNROLL_LIMIT) { // unroll the loop diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnGenerateId.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnGenerateId.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnGenerateId.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnGenerateId.java 2015-07-14 10:54:40.000000000 +0000 @@ -17,7 +17,7 @@ public final class FnGenerateId extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final ANode node = toEmptyNode(arg(0, qc), qc); + final ANode node = toEmptyNode(ctxArg(0, qc), qc); return node == null ? Str.ZERO : Str.get(new TokenBuilder(QueryText.ID).addInt(node.id).finish()); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnHasChildren.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnHasChildren.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnHasChildren.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnHasChildren.java 2015-07-14 10:54:40.000000000 +0000 @@ -17,7 +17,7 @@ public final class FnHasChildren extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final ANode node = toEmptyNode(arg(0, qc), qc); + final ANode node = toEmptyNode(ctxArg(0, qc), qc); return Bln.get(node != null && node.hasChildren()); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnId.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnId.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnId.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnId.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,9 +11,7 @@ */ public final class FnId extends Ids { @Override - public Iter iter(final QueryContext qc) throws QueryException { - final NodeSeqBuilder nc = new NodeSeqBuilder().check(); - add(ids(exprs[0].atomIter(qc, info)), nc, checkRoot(toNode(arg(1, qc), qc)), false); - return nc; + public BasicNodeIter iter(final QueryContext qc) throws QueryException { + return ids(qc, false); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnIdref.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnIdref.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnIdref.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnIdref.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,9 +11,7 @@ */ public final class FnIdref extends Ids { @Override - public Iter iter(final QueryContext qc) throws QueryException { - final NodeSeqBuilder nb = new NodeSeqBuilder().check(); - add(ids(exprs[0].atomIter(qc, info)), nb, checkRoot(toNode(arg(1, qc), qc)), true); - return nb; + public BasicNodeIter iter(final QueryContext qc) throws QueryException { + return ids(qc, true); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnInScopePrefixes.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnInScopePrefixes.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnInScopePrefixes.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnInScopePrefixes.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,6 +6,7 @@ import org.basex.query.*; import org.basex.query.func.*; import org.basex.query.iter.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.util.*; @@ -18,13 +19,18 @@ public final class FnInScopePrefixes extends StandardFunc { @Override public Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); + } + + @Override + public Value value(final QueryContext qc) throws QueryException { final Atts ns = toElem(exprs[0], qc).nsScope(sc).add(XML, XML_URI); final int as = ns.size(); - final ValueBuilder vb = new ValueBuilder(as); + final ValueBuilder vb = new ValueBuilder(); for(int a = 0; a < as; ++a) { final byte[] key = ns.name(a); if(key.length + ns.value(a).length != 0) vb.add(Str.get(key)); } - return vb; + return vb.value(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnInsertBefore.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnInsertBefore.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnInsertBefore.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnInsertBefore.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,7 +3,9 @@ import org.basex.query.*; import org.basex.query.func.*; import org.basex.query.iter.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; +import org.basex.query.value.seq.*; /** * Function implementation. @@ -15,22 +17,39 @@ @Override public Iter iter(final QueryContext qc) throws QueryException { return new Iter() { - final long pos = Math.max(1, toLong(exprs[1], qc)); final Iter iter = exprs[0].iter(qc); + final long pos = Math.max(1, toLong(exprs[1], qc)); final Iter ins = exprs[2].iter(qc); long p = pos; boolean last; @Override public Item next() throws QueryException { - if(last) return p > 0 ? ins.next() : null; - final boolean sub = p == 0 || --p == 0; - final Item i = (sub ? ins : iter).next(); - if(i != null) return i; - if(sub) --p; - else last = true; - return next(); + while(!last) { + final boolean sub = p == 0 || --p == 0; + final Item i = (sub ? ins : iter).next(); + if(i != null) return i; + if(sub) --p; + else last = true; + } + return p > 0 ? ins.next() : null; } }; } + + @Override + public Value value(final QueryContext qc) throws QueryException { + final Value val = exprs[0].value(qc); + final long pos = toLong(exprs[1], qc); + final Value sub = exprs[2].value(qc); + + final long vs = val.size(); + final long p = Math.min(Math.max(0, pos - 1), vs); + + // prepend, append or insert new value + if(p == 0) return ValueBuilder.concat(sub, val); + if(p == vs) return ValueBuilder.concat(val, sub); + return ((Seq) val).insertBefore(p, sub); + } } + diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnJsonDoc.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnJsonDoc.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnJsonDoc.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnJsonDoc.java 2015-07-14 10:54:40.000000000 +0000 @@ -13,6 +13,7 @@ public final class FnJsonDoc extends FnParseJson { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - return parse(unparsedText(qc, false).string(ii), qc, ii); + final Item it = unparsedText(qc, false, false); + return it == null ? null : parse(it.string(ii), false, qc, ii); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnJsonToXml.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnJsonToXml.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnJsonToXml.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnJsonToXml.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,20 @@ +package org.basex.query.func.fn; + +import org.basex.query.*; +import org.basex.query.value.item.*; +import org.basex.util.*; + +/** + * Function implementation. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public class FnJsonToXml extends FnParseJson { + @Override + public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { + final Item it = exprs[0].atomItem(qc, info); + if(it == null) return null; + return parse(toToken(it), true, qc, ii); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnLang.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnLang.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnLang.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnLang.java 2015-07-14 10:54:40.000000000 +0000 @@ -19,9 +19,9 @@ @Override public Bln item(final QueryContext qc, final InputInfo ii) throws QueryException { final byte[] lang = lc(toEmptyToken(exprs[0], qc)); - final ANode node = toNode(arg(1, qc), qc); + final ANode node = toNode(ctxArg(1, qc), qc); for(ANode n = node; n != null; n = n.parent()) { - final AxisIter atts = n.attributes(); + final BasicNodeIter atts = n.attributes(); for(ANode at; (at = atts.next()) != null;) { if(eq(at.qname().string(), LANG)) { final byte[] ln = lc(normalize(at.string())); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnLocalName.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnLocalName.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnLocalName.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnLocalName.java 2015-07-14 10:54:40.000000000 +0000 @@ -17,7 +17,7 @@ public final class FnLocalName extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final ANode node = toEmptyNode(arg(0, qc), qc); + final ANode node = toEmptyNode(ctxArg(0, qc), qc); final QNm qname = node != null ? node.qname() : null; return qname != null ? Str.get(qname.local()) : Str.ZERO; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnName.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnName.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnName.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnName.java 2015-07-14 10:54:40.000000000 +0000 @@ -17,7 +17,7 @@ public final class FnName extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final ANode node = toEmptyNode(arg(0, qc), qc); + final ANode node = toEmptyNode(ctxArg(0, qc), qc); final QNm qname = node != null ? node.qname() : null; return qname != null ? Str.get(qname.string()) : Str.ZERO; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNamespaceUriForPrefix.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNamespaceUriForPrefix.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNamespaceUriForPrefix.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNamespaceUriForPrefix.java 2015-07-14 10:54:40.000000000 +0000 @@ -22,7 +22,6 @@ final ANode an = toElem(exprs[1], qc); if(eq(pref, XML)) return Uri.uri(XML_URI, false); final Atts at = an.nsScope(sc); - sc.ns.inScope(at); final byte[] s = at.value(pref); return s == null || s.length == 0 ? null : Uri.uri(s, false); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNamespaceUriFromQName.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNamespaceUriFromQName.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNamespaceUriFromQName.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNamespaceUriFromQName.java 2015-07-14 10:54:40.000000000 +0000 @@ -16,7 +16,7 @@ public final class FnNamespaceUriFromQName extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final QNm qnm = toQNm(arg(0, qc), qc, true); + final QNm qnm = toQNm(ctxArg(0, qc), qc, true); return qnm == null ? null : Uri.uri(qnm.uri()); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNamespaceUri.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNamespaceUri.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNamespaceUri.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNamespaceUri.java 2015-07-14 10:54:40.000000000 +0000 @@ -17,7 +17,7 @@ public final class FnNamespaceUri extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final ANode node = toEmptyNode(arg(0, qc), qc); + final ANode node = toEmptyNode(ctxArg(0, qc), qc); final QNm qname = node != null ? node.qname() : null; return qname != null ? Uri.uri(qname.uri(), false) : Uri.EMPTY; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNilled.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNilled.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNilled.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNilled.java 2015-07-14 10:54:40.000000000 +0000 @@ -18,7 +18,7 @@ public final class FnNilled extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final ANode node = toEmptyNode(arg(0, qc), qc); + final ANode node = toEmptyNode(ctxArg(0, qc), qc); // always false, as no schema information is given return node == null || node.type != NodeType.ELM ? null : Bln.FALSE; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNodeName.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNodeName.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNodeName.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNodeName.java 2015-07-14 10:54:40.000000000 +0000 @@ -17,7 +17,7 @@ public final class FnNodeName extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final ANode node = toEmptyNode(arg(0, qc), qc); + final ANode node = toEmptyNode(ctxArg(0, qc), qc); final QNm qname = node != null ? node.qname() : null; return qname != null && qname.string().length != 0 ? qname : null; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNormalizeSpace.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNormalizeSpace.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNormalizeSpace.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNormalizeSpace.java 2015-07-14 10:54:40.000000000 +0000 @@ -16,7 +16,7 @@ public final class FnNormalizeSpace extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - return Str.get(Token.normalize(toEmptyToken(arg(0, qc), qc))); + return Str.get(Token.normalize(toEmptyToken(ctxArg(0, qc), qc))); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNumber.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNumber.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnNumber.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnNumber.java 2015-07-14 10:54:40.000000000 +0000 @@ -17,7 +17,7 @@ public final class FnNumber extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final Item it = arg(0, qc).atomItem(qc, info); + final Item it = ctxArg(0, qc).atomItem(qc, info); if(it == null) return Dbl.NAN; if(it.type == AtomType.DBL) return it; try { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnParseJson.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnParseJson.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnParseJson.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnParseJson.java 2015-07-14 10:54:40.000000000 +0000 @@ -19,23 +19,27 @@ * @author Christian Gruen */ public class FnParseJson extends Parse { + /** Function taking and returning a string. */ + private static final FuncType STRFUNC = FuncType.get(SeqType.STR, SeqType.STR); + @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { final Item it = exprs[0].atomItem(qc, info); if(it == null) return null; - return parse(toToken(it), qc, ii); + return parse(toToken(it), false, qc, ii); } /** * Parses the specified JSON string. * @param json json string + * @param xml convert to xml * @param qc query context * @param ii input info * @return resulting item * @throws QueryException query exception */ - final Item parse(final byte[] json, final QueryContext qc, final InputInfo ii) - throws QueryException { + final Item parse(final byte[] json, final boolean xml, final QueryContext qc, + final InputInfo ii) throws QueryException { final JsonParserOptions opts = new JsonParserOptions(); if(exprs.length > 1) { @@ -49,19 +53,25 @@ final boolean unesc = opts.get(JsonParserOptions.UNESCAPE); final FuncItem fb = opts.get(JsonParserOptions.FALLBACK); - if(fb != null) { - final Type type = FuncType.get(SeqType.STR, SeqType.STR); - if(!fb.type.instanceOf(type)) throw JSON_FUNC_OPT_X_X.get(ii, type, fb.type); + final FItem fallback; + if(fb == null) { + fallback = null; + } else { + try { + fallback = STRFUNC.cast(fb, qc, sc, ii); + } catch(final QueryException ex) { + throw JSON_OPT_X.get(ii, ex.getLocalizedMessage()); + } } try { - opts.set(JsonOptions.FORMAT, JsonFormat.MAP); + opts.set(JsonOptions.FORMAT, xml ? JsonFormat.BASIC : JsonFormat.MAP); final JsonConverter conv = JsonConverter.get(opts); - if(unesc && fb != null) conv.fallback(new JsonFallback() { + if(unesc && fallback != null) conv.fallback(new JsonFallback() { @Override - public byte[] convert(final byte[] string) { + public String convert(final String string) { try { - return fb.invokeItem(qc, ii, Str.get(string)).string(ii); + return Token.string(fallback.invokeItem(qc, ii, Str.get(string)).string(ii)); } catch(final QueryException ex) { throw new QueryRTException(ex); } @@ -69,7 +79,11 @@ }); return conv.convert(json, null); } catch(final QueryRTException ex) { - throw ex.getCause(); + final QueryException qe = ex.getCause(); + final QueryError err = qe.error(); + if(err != INVPROMOTE_X_X && err != INVPROMOTE_X_X_X) throw qe; + Util.debug(ex); + throw JSON_OPT_X.get(ii, qe.getLocalizedMessage()); } catch(final QueryIOException ex) { Util.debug(ex); final QueryException qe = ex.getCause(info); @@ -77,6 +91,7 @@ final String message = ex.getLocalizedMessage(); if(error == BXJS_PARSE_X_X_X) throw JSON_PARSE_X.get(ii, message); if(error == BXJS_DUPLICATE_X) throw JSON_DUPLICATE_X.get(ii, message); + if(error == BXJS_INVALID_X) throw JSON_OPT_X.get(ii, message); throw qe; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnPath.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnPath.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnPath.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnPath.java 2015-07-14 10:54:40.000000000 +0000 @@ -20,7 +20,7 @@ public final class FnPath extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - ANode node = toEmptyNode(arg(0, qc), qc); + ANode node = toEmptyNode(ctxArg(0, qc), qc); if(node == null) return null; final TokenList tl = new TokenList(); @@ -35,21 +35,21 @@ tb.add(qnm.local()); } else if(node.type == NodeType.ELM) { final QNm qnm = node.qname(); - final AxisIter ai = node.precedingSibling(); - for(ANode fs; (fs = ai.next()) != null;) { + final BasicNodeIter iter = node.precedingSibling(); + for(ANode fs; (fs = iter.next()) != null;) { final QNm q = fs.qname(); if(q != null && q.eq(qnm)) i++; } tb.add("Q{").add(qnm.uri()).add('}').add(qnm.local()); tb.add('[').add(Integer.toString(i)).add(']'); } else if(node.type == NodeType.COM || node.type == NodeType.TXT) { - final AxisIter ai = node.precedingSibling(); - for(ANode fs; (fs = ai.next()) != null;) if(fs.type == node.type) i++; + final BasicNodeIter iter = node.precedingSibling(); + for(ANode fs; (fs = iter.next()) != null;) if(fs.type == node.type) i++; tb.addExt(node.seqType() + "[%]", i); } else if(node.type == NodeType.PI) { final QNm qnm = node.qname(); - final AxisIter ai = node.precedingSibling(); - for(ANode fs; (fs = ai.next()) != null;) { + final BasicNodeIter iter = node.precedingSibling(); + for(ANode fs; (fs = iter.next()) != null;) { if(fs.type == node.type && fs.qname().eq(qnm)) i++; } tb.add(node.type.string()).add('(').add(qnm.local()); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnPut.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnPut.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnPut.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnPut.java 2015-07-14 10:54:40.000000000 +0000 @@ -36,7 +36,7 @@ // check if all target paths are unique if(!updates.putPaths.add(uri)) throw UPURIDUP_X.get(info, uri); - updates.add(new Put(target.pre, target.data, uri, info), qc); + updates.add(new Put(target.pre(), target.data(), uri, info), qc); return null; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnRandomNumberGenerator.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnRandomNumberGenerator.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnRandomNumberGenerator.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnRandomNumberGenerator.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,10 +2,10 @@ import org.basex.query.*; import org.basex.query.func.*; +import org.basex.query.util.list.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.map.Map; -import org.basex.query.value.seq.*; import org.basex.util.*; /** @@ -42,7 +42,7 @@ public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { final long seed; if(exprs.length > 0) { - seed = exprs[0].atomItem(qc, info).hash(ii); + seed = toAtomItem(exprs[0], qc).hash(ii); } else { seed = qc.initDateTime().nano; } @@ -74,11 +74,10 @@ return RuntimeExpr.funcItem(new RuntimeExpr(info) { @Override public Value value(final QueryContext qc) throws QueryException { - final Value value = qc.get(params[0]); - final int sz = (int) value.size(); - final Item[] items = new Item[sz]; - value.writeTo(items, 0); + final ItemList cache = qc.get(params[0]).cache(); + final int sz = cache.size(); + final Item[] items = cache.internal(); long s = seed; for(int i = sz; --i >= 1;) { s = next(s); @@ -89,7 +88,7 @@ items[j] = item; } } - return Seq.get(items); + return cache.value(); } }, 1, sc, qctx); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnRemove.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnRemove.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnRemove.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnRemove.java 2015-07-14 10:54:40.000000000 +0000 @@ -35,12 +35,10 @@ @Override public Value value(final QueryContext qc) throws QueryException { final Value val = qc.value(exprs[0]); - final long p = toLong(exprs[1], qc) - 1, vs = val.size() - 1; - if(p < 0 || p > vs) return val; - if(p == 0 || p == vs) return SubSeq.get(val, p == 0 ? 1 : 0, vs); - final ValueBuilder vb = new ValueBuilder((int) vs); - for(int v = 0; v <= vs; v++) if(v != p) vb.add(val.itemAt(v)); - return vb.value(); + final long pos = toLong(exprs[1], qc) - 1, n = val.size(); + if(pos < 0 || pos >= n) return val; + if(pos == 0 || pos + 1 == n) return val.subSeq(pos == 0 ? 1 : 0, n - 1); + return ((Seq) val).remove(pos); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnReverse.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnReverse.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnReverse.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnReverse.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,7 +8,6 @@ import org.basex.query.value.item.*; import org.basex.query.value.seq.*; import org.basex.query.var.*; -import org.basex.util.*; /** * Function implementation. @@ -26,11 +25,9 @@ final Iter iter = qc.iter(exprs[0]); final long s = iter.size(); if(s == -1) { - // estimate result size (could be known in the original expression) - final ValueBuilder vb = new ValueBuilder(Math.max((int) exprs[0].size(), 1)); - for(Item it; (it = iter.next()) != null;) vb.add(it); - Array.reverse(vb.items(), 0, (int) vb.size()); - return vb; + final ValueBuilder vb = new ValueBuilder(); + for(Item it; (it = iter.next()) != null;) vb.addFront(it); + return vb.value().iter(); } // return iterator if items can be directly accessed @@ -47,8 +44,7 @@ @Override public Value value(final QueryContext qc) throws QueryException { - final Value v = exprs[0].value(qc); - return v.size() > 1 ? ((Seq) v).reverse() : v; + return exprs[0].value(qc).reverse(); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnRoot.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnRoot.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnRoot.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnRoot.java 2015-07-14 10:54:40.000000000 +0000 @@ -17,13 +17,8 @@ public final class FnRoot extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - ANode node = toEmptyNode(arg(0, qc), qc); - while(node != null) { - final ANode p = node.parent(); - if(p == null) break; - node = p; - } - return node; + final ANode node = toEmptyNode(ctxArg(0, qc), qc); + return node != null ? node.root() : null; } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnSort.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnSort.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnSort.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnSort.java 2015-07-14 10:54:40.000000000 +0000 @@ -42,7 +42,7 @@ public long size() { return sz; } @Override public Value value() { - final ValueBuilder vb = new ValueBuilder(sz); + final ValueBuilder vb = new ValueBuilder(); for(int r = 0; r < sz; r++) vb.add(get(r)); return vb.value(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnString.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnString.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnString.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnString.java 2015-07-14 10:54:40.000000000 +0000 @@ -21,7 +21,7 @@ public final class FnString extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final Item it = arg(0, qc).item(qc, info); + final Item it = ctxArg(0, qc).item(qc, info); if(it instanceof FItem) throw FISTRING_X.get(ii, it.type); return it == null ? Str.ZERO : it.type == AtomType.STR ? it : Str.get(it.string(ii)); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnStringLength.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnStringLength.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnStringLength.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnStringLength.java 2015-07-14 10:54:40.000000000 +0000 @@ -24,7 +24,7 @@ if(it instanceof FItem) throw FISTRING_X.get(ii, it.type); s = it == null ? Token.EMPTY : it.string(ii); } else { - s = toEmptyToken(arg(0, qc), qc); + s = toEmptyToken(ctxArg(0, qc), qc); } return Int.get(Token.length(s)); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnSubsequence.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnSubsequence.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnSubsequence.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnSubsequence.java 2015-07-14 10:54:40.000000000 +0000 @@ -31,7 +31,7 @@ // optimization: return subsequence final Iter iter = qc.iter(exprs[0]); - if(iter instanceof ValueIter) return sub((ValueIter) iter, start, len).iter(); + if(iter instanceof ValueIter) return eval(((ValueIter) iter).value(), start, len).iter(); // fast route if the size is known final long max = iter.size(); @@ -71,7 +71,7 @@ // optimization: return subsequence final Iter iter = qc.iter(exprs[0]); - if(iter instanceof ValueIter) return sub((ValueIter) iter, start, len); + if(iter instanceof ValueIter) return eval(((ValueIter) iter).value(), start, len); // fast route if the size is known final long max = iter.size(); @@ -79,7 +79,7 @@ final long s = Math.max(1, start) - 1; final long l = Math.min(max - s, len + Math.min(0, start - 1)); if(s >= max || l <= 0) return Empty.SEQ; - final ValueBuilder vb = new ValueBuilder(Math.max((int) l, 1)); + final ValueBuilder vb = new ValueBuilder(); for(long i = 0; i < l; i++) vb.add(iter.get(s + i)); return vb.value(); } @@ -124,16 +124,15 @@ /** * Returns a subsequence. - * @param iter iterator + * @param val value * @param start start position * @param len length * @return sub sequence */ - private static Value sub(final ValueIter iter, final long start, final long len) { - final Value val = iter.value(); - final long s = Math.max(1, start) - 1; - final long l = Math.min(val.size() - s, len + Math.min(0, start - 1)); - return SubSeq.get(val, s, l); + public static Value eval(final Value val, final long start, final long len) { + final long p = Math.max(0, start - 1); + final long k = Math.min(val.size() - p, len + Math.min(0, start - 1)); + return k <= 0 ? Empty.SEQ : val.subSeq(p, k); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnSubstring.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnSubstring.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnSubstring.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnSubstring.java 2015-07-14 10:54:40.000000000 +0000 @@ -21,7 +21,7 @@ // normalize positions final byte[] str = toEmptyToken(exprs[0], qc); - final Item is = toItem(exprs[1], qc); + final Item is = toAtomItem(exprs[1], qc); int s; if(is instanceof Int) { s = (int) is.itr(info) - 1; @@ -35,7 +35,7 @@ int l = ascii ? str.length : length(str); int e = l; if(end) { - final Item ie = toItem(exprs[2], qc); + final Item ie = toAtomItem(exprs[2], qc); e = ie instanceof Int ? (int) ie.itr(info) : subPos(ie.dbl(info) + 1); } if(s < 0) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnTail.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnTail.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnTail.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnTail.java 2015-07-14 10:54:40.000000000 +0000 @@ -26,7 +26,7 @@ final Iter ir = e.iter(qc); if(ir instanceof ValueIter) { final Value val = ir.value(); - return SubSeq.get(val, 1, val.size() - 1).iter(); + return val.size() < 2 ? Empty.ITER : val.subSeq(1, val.size() - 1).iter(); } if(ir.next() == null) return Empty.ITER; @@ -41,7 +41,7 @@ @Override public Value value(final QueryContext qc) throws QueryException { final Value val = qc.value(exprs[0]); - return SubSeq.get(val, 1, val.size() - 1); + return val.size() < 2 ? Empty.SEQ : val.subSeq(1, val.size() - 1); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnUnparsedTextAvailable.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnUnparsedTextAvailable.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnUnparsedTextAvailable.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnUnparsedTextAvailable.java 2015-07-14 10:54:40.000000000 +0000 @@ -13,6 +13,6 @@ public final class FnUnparsedTextAvailable extends Parse { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - return unparsedText(qc, true); + return unparsedText(qc, true, true); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnUnparsedText.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnUnparsedText.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnUnparsedText.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnUnparsedText.java 2015-07-14 10:54:40.000000000 +0000 @@ -13,6 +13,6 @@ public final class FnUnparsedText extends Parse { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - return unparsedText(qc, false); + return unparsedText(qc, false, true); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnUnparsedTextLines.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnUnparsedTextLines.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnUnparsedTextLines.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnUnparsedTextLines.java 2015-07-14 10:54:40.000000000 +0000 @@ -13,7 +13,7 @@ public final class FnUnparsedTextLines extends Parse { @Override public Iter iter(final QueryContext qc) throws QueryException { - return textIter(unparsedText(qc, false).string(info)); + return textIter(unparsedText(qc, false, true).string(info)); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnXmlToJson.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnXmlToJson.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/FnXmlToJson.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/FnXmlToJson.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,36 @@ +package org.basex.query.func.fn; + +import static org.basex.query.QueryError.*; + +import org.basex.build.json.*; +import org.basex.build.json.JsonOptions.*; +import org.basex.io.serial.*; +import org.basex.query.*; +import org.basex.query.value.item.*; +import org.basex.query.value.node.*; +import org.basex.util.*; +import org.basex.util.options.Options.*; + +/** + * Function implementation. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public class FnXmlToJson extends FnParseJson { + @Override + public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { + final ANode node = toEmptyNode(exprs[0], qc); + final JsonSerialOptions opts = toOptions(1, null, new JsonSerialOptions(), qc); + if(node == null) return null; + + final JsonSerialOptions jopts = new JsonSerialOptions(); + jopts.set(JsonOptions.FORMAT, JsonFormat.BASIC); + + final SerializerOptions sopts = new SerializerOptions(); + sopts.set(SerializerOptions.METHOD, SerialMethod.JSON); + sopts.set(SerializerOptions.JSON, jopts); + sopts.set(SerializerOptions.INDENT, opts.get(JsonSerialOptions.INDENT) ? YesNo.YES : YesNo.NO); + return Str.get(serialize(node.iter(), sopts, INVALIDOPT_X)); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/Ids.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/Ids.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/Ids.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/Ids.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,12 +4,20 @@ import static org.basex.query.QueryText.*; import static org.basex.util.Token.*; +import org.basex.core.locks.*; +import org.basex.data.*; import org.basex.query.*; +import org.basex.query.expr.*; import org.basex.query.func.*; import org.basex.query.iter.*; +import org.basex.query.util.*; +import org.basex.query.util.list.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; +import org.basex.query.value.seq.*; import org.basex.query.value.type.*; +import org.basex.util.hash.*; import org.basex.util.list.*; /** @@ -20,62 +28,108 @@ */ abstract class Ids extends StandardFunc { /** - * Extracts the ids from the specified iterator. - * @param iter iterator - * @return ids + * Returns referenced nodes. + * @param qc query context + * @param idref follow idref + * @return referenced nodes * @throws QueryException query exception */ - final byte[][] ids(final Iter iter) throws QueryException { - final TokenList tl = new TokenList(); - for(Item id; (id = iter.next()) != null;) { - for(final byte[] i : split(normalize(toToken(id)), ' ')) tl.add(i); + protected BasicNodeIter ids(final QueryContext qc, final boolean idref) throws QueryException { + // [CG] XQuery: ID-IDREF Parsing: consider schema information + + final TokenSet idSet = ids(exprs[0].atomIter(qc, info)); + final ANode root = checkRoot(toNode(ctxArg(1, qc), qc)); + + final Data data = root.data(); + if(idref || data == null) { + final ANodeList list = new ANodeList().check(); + add(idSet, list, root, idref); + return list.iter(); + } + + // database value index can be utilized. create index iterator + final TokenList idList = new TokenList(idSet.size()); + for(final byte[] id : idSet) idList.add(id); + final Value ids = StrSeq.get(idList); + final ValueAccess va = new ValueAccess(info, ids, false, null, new IndexContext(data, false)); + + // collect and return index results, filtered by id/idref attributes + final ANodeList results = new ANodeList(); + for(final ANode attr : va.iter(qc)) { + if(isId(attr, idref)) results.add(attr.parent()); } - return tl.finish(); + return results.iter(); } /** * Adds nodes with the specified id. - * @param ids ids to be found + * @param idSet ids to be found + * @param results node cache + * @param node current node * @param idref idref flag - * @param nc node cache - * @param node node */ - static void add(final byte[][] ids, final NodeSeqBuilder nc, final ANode node, - final boolean idref) { - AxisIter ai = node.attributes(); - for(ANode at; (at = ai.next()) != null;) { - final byte[][] val = split(at.string(), ' '); - // [CG] XQuery: ID-IDREF Parsing - for(final byte[] id : ids) { - if(!eq(id, val)) continue; - final byte[] nm = lc(at.qname().string()); - final boolean ii = contains(nm, ID), ir = contains(nm, IDREF); - if(idref ? ir : ii && !ir) nc.add(idref ? at.finish() : node); + private static void add(final TokenSet idSet, final ANodeList results, final ANode node, + final boolean idref) { + + for(final ANode attr : node.attributes()) { + if(isId(attr, idref)) { + // id/idref found + for(final byte[] val : split(normalize(attr.string()), ' ')) { + // correct value: add to results + if(idSet.contains(val)) { + results.add(idref ? attr.finish() : node); + break; + } + } } } - ai = node.children(); - for(ANode att; (att = ai.next()) != null;) add(ids, nc, att.finish(), idref); + for(final ANode child : node.children()) add(idSet, results, child, idref); + } + + /** + * Checks if an attribute is an id/idref attribute. + * @param attr attribute + * @param idref id/idref flag + * @return result of check + */ + private static boolean isId(final ANode attr, final boolean idref) { + final byte[] name = lc(attr.name()); + return idref ? contains(name, IDREF) : contains(name, ID) && !contains(name, IDREF); } /** * Checks if the specified node has a document node as root. * @param node input node - * @return specified node + * @return root node * @throws QueryException query exception */ - ANode checkRoot(final ANode node) throws QueryException { - if(node instanceof FNode) { - ANode n = node; - while(n.type != NodeType.DOC) { - n = n.parent(); - if(n == null) throw IDDOC.get(info); - } + private ANode checkRoot(final ANode node) throws QueryException { + final ANode root = node.root(); + if(root.type != NodeType.DOC) throw IDDOC.get(info); + return root; + } + + /** + * Extracts and returns all unique ids from the iterated strings. + * @param iter iterator + * @return id set + * @throws QueryException query exception + */ + private TokenSet ids(final Iter iter) throws QueryException { + final TokenSet ts = new TokenSet(); + for(Item ids; (ids = iter.next()) != null;) { + for(final byte[] id : split(normalize(toToken(ids)), ' ')) ts.put(id); } - return node; + return ts; } @Override public final boolean has(final Flag flag) { return flag == Flag.CTX && exprs.length == 1 || super.has(flag); } + + @Override + public final boolean accept(final ASTVisitor visitor) { + return (exprs.length != 1 || visitor.lock(DBLocking.CONTEXT)) && super.accept(visitor); + } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/Nodes.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/Nodes.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/Nodes.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/Nodes.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,9 +4,9 @@ import org.basex.query.*; import org.basex.query.func.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; -import org.basex.util.*; /** * Node functions. @@ -23,61 +23,57 @@ * @return outermost/innermost nodes * @throws QueryException exception */ - Iter most(final QueryContext qc, final boolean outer) throws QueryException { + NodeIter most(final QueryContext qc, final boolean outer) throws QueryException { final Iter iter = exprs[0].iter(qc); - final NodeSeqBuilder nc = new NodeSeqBuilder().check(); - for(Item it; (it = iter.next()) != null;) nc.add(toNode(it)); - final int len = (int) nc.size(); + final ANodeList list = new ANodeList().check(); + for(Item it; (it = iter.next()) != null;) list.add(toNode(it)); + final int len = list.size(); // only go further if there are at least two nodes - if(len < 2) return nc; + if(len < 2) return list.iter(); // after this, the iterator is sorted and duplicate free - if(nc.dbnodes()) { + final ANodeList res = new ANodeList().check(); + if(list.dbnodes()) { // nodes are sorted, so ancestors always come before their descendants // the first/last node is thus always included in the output - final DBNode fst = (DBNode) nc.get(outer ? 0 : len - 1); - final Data data = fst.data; - final ANode[] nodes = nc.nodes.clone(); + final DBNode fst = (DBNode) list.get(outer ? 0 : len - 1); + final Data data = fst.data(); if(outer) { // skip the subtree of the last added node - nc.size(0); - final DBNode dummy = new DBNode(fst.data); - final NodeSeqBuilder src = new NodeSeqBuilder(nodes, len); + final DBNode dummy = new DBNode(data); for(int next = 0, p; next < len; next = p < 0 ? -p - 1 : p) { - final DBNode nd = (DBNode) nodes[next]; - dummy.pre = nd.pre + data.size(nd.pre, data.kind(nd.pre)); - p = src.binarySearch(dummy, next + 1, len - next - 1); - nc.add(nd); + final DBNode nd = (DBNode) list.get(next); + final int pre = nd.pre(); + dummy.pre(pre + data.size(pre, data.kind(pre))); + p = list.binarySearch(dummy, next + 1, len - next - 1); + res.add(nd); } } else { // skip ancestors of the last added node - nc.nodes[0] = fst; - nc.size(1); - int before = fst.pre; + res.add(fst); + int before = fst.pre(); for(int i = len - 1; i-- != 0;) { - final DBNode nd = (DBNode) nodes[i]; - if(nd.pre + data.size(nd.pre, data.kind(nd.pre)) <= before) { - nc.add(nd); - before = nd.pre; + final DBNode nd = (DBNode) list.get(i); + final int pre = nd.pre(); + if(pre + data.size(pre, data.kind(pre)) <= before) { + res.add(nd); + before = pre; } } - // nodes were added in reverse order, correct that - Array.reverse(nc.nodes, 0, (int) nc.size()); } - return nc; - } - - // multiple documents and/or constructed fragments - final NodeSeqBuilder out = new NodeSeqBuilder(new ANode[len], 0); - OUTER: for(int i = 0; i < len; i++) { - final ANode nd = nc.nodes[i]; - final AxisIter ax = outer ? nd.ancestor() : nd.descendant(); - for(ANode a; (a = ax.next()) != null;) - if(nc.indexOf(a, false) != -1) continue OUTER; - out.add(nd); + } else { + // multiple documents and/or constructed fragments + OUTER: for(int i = 0; i < len; i++) { + final ANode nd = list.get(i); + final BasicNodeIter ax = outer ? nd.ancestor() : nd.descendant(); + for(ANode a; (a = ax.next()) != null;) { + if(list.indexOf(a, false) != -1) continue OUTER; + } + res.add(nd); + } } - return out; + return res.iter(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/Parse.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/Parse.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/fn/Parse.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/fn/Parse.java 2015-07-14 10:54:40.000000000 +0000 @@ -28,18 +28,24 @@ * Performs the unparsed-text function. * @param qc query context * @param check only check if text is available + * @param encoding parse encoding * @return content string or boolean success flag * @throws QueryException query exception */ - Item unparsedText(final QueryContext qc, final boolean check) throws QueryException { + Item unparsedText(final QueryContext qc, final boolean check, final boolean encoding) + throws QueryException { + checkCreate(qc); - final byte[] path = toToken(exprs[0], qc); + final Item it = exprs[0].atomItem(qc, info); + if(it == null) return check ? Bln.FALSE : null; + + final byte[] path = toToken(it); final IO base = sc.baseIO(); String enc = null; try { if(base == null) throw STBASEURI.get(info); - enc = toEncoding(1, ENCODING_X, qc); + enc = encoding ? toEncoding(1, ENCODING_X, qc) : null; final String p = string(path); if(p.indexOf('#') != -1) throw FRAGID_X.get(info, p); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/ft/FtCount.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/ft/FtCount.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/ft/FtCount.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/ft/FtCount.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,8 +1,8 @@ package org.basex.query.func.ft; -import org.basex.data.*; import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.util.ft.*; import org.basex.query.value.item.*; import org.basex.util.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/ft/FtMark.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/ft/FtMark.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/ft/FtMark.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/ft/FtMark.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,6 +7,7 @@ import org.basex.query.func.*; import org.basex.query.iter.*; import org.basex.query.util.*; +import org.basex.query.util.ft.*; import org.basex.query.value.item.*; import org.basex.query.value.seq.*; import org.basex.query.value.type.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/FuncLit.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/FuncLit.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/FuncLit.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/FuncLit.java 2015-07-14 10:54:40.000000000 +0000 @@ -105,7 +105,7 @@ @Override public boolean has(final Flag flag) { - return flag == Flag.CTX || flag == Flag.FCS; + return flag == Flag.CTX || flag == Flag.POS; } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/FuncOptions.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/FuncOptions.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/FuncOptions.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/FuncOptions.java 2015-07-14 10:54:40.000000000 +0000 @@ -25,7 +25,7 @@ /** QName. */ public static final QNm Q_SPARAM = QNm.get(SERIALIZATION_PARAMETERS, OUTPUT_URI); /** Value. */ - private static final String VALUE = "value"; + private static final byte[] VALUE = token("value"); /** Root element. */ private final QNm root; @@ -99,7 +99,8 @@ } else if(!test.eq(item)) { throw ELMMAP_X_X_X.get(info, root.prefixId(XML), item.type, item); } - options.parse(tb.add(optString((ANode) item)).toString()); + final String opts = optString((ANode) item, error); + options.parse(tb.add(opts).toString()); } } catch(final BaseXException ex) { throw error.get(info, ex); @@ -111,26 +112,42 @@ /** * Builds a string representation of the specified node. * @param node node + * @param error raise error code * @return string * @throws QueryException query exception */ - private String optString(final ANode node) throws QueryException { + private String optString(final ANode node, final QueryError error) throws QueryException { + final ANode n = node.attributes().next(); + if(n != null) throw error.get(info, Util.info("Invalid attribute: '%'", n.name())); + final TokenBuilder tb = new TokenBuilder(); // interpret options for(final ANode child : node.children()) { if(child.type != NodeType.ELM) continue; + // ignore elements in other namespace final QNm qn = child.qname(); - if(!eq(qn.uri(), root.uri())) continue; + if(!eq(qn.uri(), root.uri())) { + if(qn.uri().length == 0) + throw error.get(info, Util.info("Element has no namespace: '%'", qn)); + continue; + } // retrieve key from element name and value from "value" attribute or text node - byte[] v; + byte[] v = null; if(hasElements(child)) { - v = token(optString(child)); + v = token(optString(child, error)); } else { - v = child.attribute(VALUE); + for(final ANode attr : child.attributes()) { + if(eq(attr.name(), VALUE)) { + v = attr.string(); + } else { + // Conflicts with QT3TS, Serialization-json-34 etc. + //throw error.get(info, Util.info("Invalid attribute: '%'", attr.name())); + } + } if(v == null) v = child.string(); } - tb.add(string(qn.local())).add('=').add(string(v).replace(",", ",,")).add(','); + tb.add(string(qn.local())).add('=').add(string(v).trim().replace(",", ",,")).add(','); } return tb.toString(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/Function.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/Function.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/Function.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/Function.java 2015-07-14 10:54:40.000000000 +0000 @@ -57,7 +57,7 @@ /** * Definitions of all built-in XQuery functions. * New namespace mappings for function prefixes and URIs must be added to the static initializer of - * the {@code NSGlobal} class. + * the {@link NSGlobal} class. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen @@ -204,7 +204,7 @@ FUNCTION_ARITY(FnFunctionArity.class, "function-arity(function)", arg(FUN_O), ITR), /** XQuery function. */ FUNCTION_LOOKUP(FnFunctionLookup.class, "function-lookup(name,arity)", - arg(QNM, ITR), FUN_OZ, flag(CTX, FCS, NDT, HOF)), + arg(QNM, ITR), FUN_OZ, flag(CTX, Flag.POS, NDT, HOF)), /** XQuery function. */ FUNCTION_NAME(FnFunctionName.class, "function-name(function)", arg(FUN_O), QNM_ZO), /** XQuery function. */ @@ -243,7 +243,7 @@ /** XQuery function. */ LANG(FnLang.class, "lang(ids[,node])", arg(STR_ZO, NOD), BLN), /** XQuery function. */ - LAST(FnLast.class, "last()", arg(), ITR, flag(FCS, CTX)), + LAST(FnLast.class, "last()", arg(), ITR, flag(Flag.POS, CTX)), /** XQuery function. */ LOCAL_NAME(FnLocalName.class, "local-name([node])", arg(NOD_ZO), STR), /** XQuery function. */ @@ -303,7 +303,11 @@ /** XQuery function. */ PARSE_IETF_DATE(FnParseIetfDate.class, "parse-ietf-date(string)", arg(STR_ZO), DTM_ZO), /** XQuery function. */ - PARSE_JSON(FnParseJson.class, "parse-json(string[,options])", arg(STR_ZO, ITEM), DTM_ZO), + JSON_TO_XML(FnJsonToXml.class, "json-to-xml(string[,options])", arg(STR_ZO, MAP_O), NOD_ZO), + /** XQuery function. */ + XML_TO_JSON(FnXmlToJson.class, "xml-to-json(node[,options])", arg(NOD_ZO, MAP_O), STR_ZO), + /** XQuery function. */ + PARSE_JSON(FnParseJson.class, "parse-json(string[,options])", arg(STR_ZO, ITEM), ITEM_ZO), /** XQuery function. */ PARSE_XML(FnParseXml.class, "parse-xml(string)", arg(STR_ZO), DOC_O, flag(CNS)), /** XQuery function. */ @@ -311,7 +315,7 @@ /** XQuery function. */ PATH(FnPath.class, "path([node])", arg(NOD_ZO), STR_ZO), /** XQuery function. */ - POSITION(FnPosition.class, "position()", arg(), ITR, flag(FCS, CTX)), + POSITION(FnPosition.class, "position()", arg(), ITR, flag(Flag.POS, CTX)), /** XQuery function. */ PREFIX_FROM_QNAME(FnPrefixFromQName.class, "prefix-from-QName(qname)", arg(QNM_ZO), NCN_ZO), /** XQuery function. */ @@ -464,11 +468,11 @@ /** XQuery function. */ _ARRAY_HEAD(ArrayHead.class, "head(array)", arg(ARRAY_O), ITEM_ZM, ARRAY_URI), /** XQuery function. */ - _ARRAY_TAIL(ArrayTail.class, "tail(array)", arg(ARRAY_O), ITEM_ZM, ARRAY_URI), + _ARRAY_TAIL(ArrayTail.class, "tail(array)", arg(ARRAY_O), ARRAY_O, ARRAY_URI), /** XQuery function. */ - _ARRAY_REVERSE(ArrayReverse.class, "reverse(array)", arg(ARRAY_O), ITEM_ZM, ARRAY_URI), + _ARRAY_REVERSE(ArrayReverse.class, "reverse(array)", arg(ARRAY_O), ARRAY_O, ARRAY_URI), /** XQuery function. */ - _ARRAY_JOIN(ArrayJoin.class, "join(array)", arg(ARRAY_ZM), ITEM_ZM, ARRAY_URI), + _ARRAY_JOIN(ArrayJoin.class, "join(array)", arg(ARRAY_ZM), ARRAY_O, ARRAY_URI), /** XQuery function. */ _ARRAY_FLATTEN(ArrayFlatten.class, "flatten(item()*)", arg(ITEM_ZM), ITEM_ZM, ARRAY_URI), /** XQuery function. */ @@ -545,7 +549,8 @@ /** XQuery function. */ _ADMIN_LOGS(AdminLogs.class, "logs([date[,merge]])", arg(STR, BLN), ELM_ZM, flag(NDT), ADMIN_URI), /** XQuery function. */ - _ADMIN_WRITE_LOG(AdminWriteLog.class, "write-log(string)", arg(STR), EMP, flag(NDT), ADMIN_URI), + _ADMIN_WRITE_LOG(AdminWriteLog.class, "write-log(message[,type])", + arg(STR, STR), EMP, flag(NDT), ADMIN_URI), /** XQuery function. */ _ADMIN_DELETE_LOGS(AdminDeleteLogs.class, "delete-logs(date)", arg(STR), EMP, flag(NDT), ADMIN_URI), @@ -556,6 +561,9 @@ _ARCHIVE_CREATE(ArchiveCreate.class, "create(entries,contents[,options])", arg(ITEM_ZM, ITEM_ZM, ITEM), B64, ARCHIVE_URI), /** XQuery function. */ + _ARCHIVE_CREATE_FROM(ArchiveCreateFrom.class, "create-from(path[,options[,entries]])", + arg(STR, ITEM, ITEM_ZM), EMP, ARCHIVE_URI), + /** XQuery function. */ _ARCHIVE_ENTRIES(ArchiveEntries.class, "entries(archive)", arg(B64), ELM_ZM, ARCHIVE_URI), /** XQuery function. */ _ARCHIVE_EXTRACT_TEXT(ArchiveExtractText.class, "extract-text(archive[,entries[,encoding]])", @@ -564,6 +572,9 @@ _ARCHIVE_EXTRACT_BINARY(ArchiveExtractBinary.class, "extract-binary(archive[,entries])", arg(B64, ITEM_ZM), B64_ZM, ARCHIVE_URI), /** XQuery function. */ + _ARCHIVE_EXTRACT_TO(ArchiveExtractTo.class, "extract-to(path,archive[,entries])", + arg(STR, B64, ITEM_ZM), EMP, ARCHIVE_URI), + /** XQuery function. */ _ARCHIVE_UPDATE(ArchiveUpdate.class, "update(archive,entries,contents)", arg(B64, ITEM_ZM, ITEM_ZM), B64, ARCHIVE_URI), /** XQuery function. */ @@ -571,8 +582,8 @@ arg(B64, ITEM_ZM), B64, ARCHIVE_URI), /** XQuery function. */ _ARCHIVE_OPTIONS(ArchiveOptions.class, "options(archive)", arg(B64), ELM, ARCHIVE_URI), - /** XQuery function. */ - _ARCHIVE_WRITE(ArchiveWrite.class, "write(path,archive[,entries])", + /** XQuery function (deprecated). */ + @Deprecated _ARCHIVE_WRITE(ArchiveWrite.class, "write(path,archive[,entries])", arg(STR, B64, ITEM_ZM), EMP, ARCHIVE_URI), /* BaseX Module. */ @@ -786,10 +797,10 @@ /** XQuery function. */ _DB_NODE_PRE(DbNodePre.class, "node-pre(nodes)", arg(NOD_ZM), ITR_ZM, DB_URI), /** XQuery function. */ - _DB_EVENT(DbEvent.class, "event(name,query)", arg(STR, ITEM_ZM), EMP, flag(NDT), DB_URI), - /** XQuery function. */ _DB_OUTPUT(DbOutput.class, "output(result)", arg(ITEM_ZM), EMP, flag(UPD, NDT), DB_URI), /** XQuery function. */ + _DB_OUTPUT_CACHE(DbOutputCache.class, "output-cache()", arg(), ITEM_ZO, flag(NDT), DB_URI), + /** XQuery function. */ _DB_ADD(DbAdd.class, "add(database,input[,path[,options]])", arg(STR, NOD, STR, ITEM), EMP, flag(UPD, NDT), DB_URI), /** XQuery function. */ @@ -864,6 +875,8 @@ /** XQuery function. */ _FILE_IS_DIR(FileIsDir.class, "is-dir(path)", arg(STR), BLN, flag(NDT), FILE_URI), /** XQuery function. */ + _FILE_IS_ABSOLUTE(FileIsAbsolute.class, "is-absolute(path)", arg(STR), BLN, flag(NDT), FILE_URI), + /** XQuery function. */ _FILE_IS_FILE(FileIsFile.class, "is-file(path)", arg(STR), BLN, flag(NDT), FILE_URI), /** XQuery function. */ _FILE_LAST_MODIFIED(FileLastModified.class, "last-modified(path)", @@ -874,8 +887,8 @@ _FILE_PATH_TO_NATIVE(FilePathToNative.class, "path-to-native(path)", arg(STR), STR, flag(NDT), FILE_URI), /** XQuery function. */ - _FILE_RESOLVE_PATH(FileResolvePath.class, "resolve-path(path)", - arg(STR), STR, flag(NDT), FILE_URI), + _FILE_RESOLVE_PATH(FileResolvePath.class, "resolve-path(path[,base])", + arg(STR, STR), STR, flag(NDT), FILE_URI), /** XQuery function. */ _FILE_LIST(FileList.class, "list(path[,recursive[,pattern]])", arg(STR, BLN, STR), STR_ZM, flag(NDT), FILE_URI), @@ -1202,6 +1215,12 @@ /** XQuery function. */ _VALIDATE_DTD_INFO(ValidateDtdInfo.class, "dtd-info(input[,schema])", arg(ITEM, ITEM), STR_ZM, flag(NDT), VALIDATE_URI), + /** XQuery function. */ + _VALIDATE_RNG(ValidateRng.class, "rng(input,schema[,compact])", + arg(ITEM, ITEM, BLN), STR_ZM, flag(NDT), VALIDATE_URI), + /** XQuery function. */ + _VALIDATE_RNG_INFO(ValidateRngInfo.class, "rng-info(input,schema[,compact])", + arg(ITEM, ITEM, BLN), STR_ZM, flag(NDT), VALIDATE_URI), /* Web Module. */ @@ -1214,6 +1233,10 @@ /** XQuery function. */ _WEB_RESPONSE_HEADER(WebResponseHeader.class, "response-header([headers[,output]])", arg(MAP_O, MAP_O), ELM, WEB_URI), + /** XQuery function. */ + _WEB_ENCODE_URL(WebEncodeUrl.class, "encode-url(string)", arg(STR), STR, WEB_URI), + /** XQuery function. */ + _WEB_DECODE_URL(WebDecodeUrl.class, "decode-url(string)", arg(STR), STR, WEB_URI), /* XQuery Module. */ diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/Functions.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/Functions.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/Functions.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/Functions.java 2015-07-14 10:54:40.000000000 +0000 @@ -187,7 +187,7 @@ qc.updating(); anns.add(new Ann(ii, Annotation.UPDATING)); } - if(!sf.has(Flag.CTX) && !sf.has(Flag.FCS)) return closureOrFItem( + if(!sf.has(Flag.CTX) && !sf.has(Flag.POS)) return closureOrFItem( anns, name, args, fn.type(arity, anns), sf, scp, sc, ii, runtime, upd); return new FuncLit(anns, name, args, sf, ft, scp, sc, ii); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/hof/HofSortWith.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/hof/HofSortWith.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/hof/HofSortWith.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/hof/HofSortWith.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,6 +4,7 @@ import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.*; import org.basex.query.value.item.*; @@ -24,12 +25,12 @@ final Value v = exprs[0].value(qc); final Comparator cmp = getComp(1, qc); if(v.size() < 2) return v; - final ValueBuilder vb = v.cache(); + final ItemList items = v.cache(); try { - Arrays.sort(vb.items(), 0, (int) vb.size(), cmp); + Arrays.sort(items.internal(), 0, items.size(), cmp); } catch(final QueryRTException ex) { throw ex.getCause(); } - return vb.value(); + return items.value(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/hof/HofTakeWhile.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/hof/HofTakeWhile.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/hof/HofTakeWhile.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/hof/HofTakeWhile.java 2015-07-14 10:54:40.000000000 +0000 @@ -24,7 +24,7 @@ @Override public Item next() throws QueryException { final Item it = in.next(); - if(it != null && pred.invokeValue(qc, info, it).ebv(qc, info).bool(info)) return it; + if(it != null && toBoolean(pred.invokeValue(qc, info, it), qc)) return it; return null; } }; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/hof/HofTopKBy.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/hof/HofTopKBy.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/hof/HofTopKBy.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/hof/HofTopKBy.java 2015-07-14 10:54:40.000000000 +0000 @@ -48,8 +48,8 @@ } } catch(final QueryRTException ex) { throw ex.getCause(); } - final Item[] arr = new Item[heap.size()]; - for(int i = arr.length; --i >= 0;) arr[i] = heap.removeMin(); - return Seq.get(arr); + final ValueBuilder vb = new ValueBuilder(); + while(!heap.isEmpty()) vb.addFront(heap.removeMin()); + return vb.value(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/hof/HofTopKWith.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/hof/HofTopKWith.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/hof/HofTopKWith.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/hof/HofTopKWith.java 2015-07-14 10:54:40.000000000 +0000 @@ -37,8 +37,8 @@ } } catch(final QueryRTException ex) { throw ex.getCause(); } - final Item[] arr = new Item[heap.size()]; - for(int i = arr.length; --i >= 0;) arr[i] = heap.removeMin(); - return Seq.get(arr); + final ValueBuilder vb = new ValueBuilder(); + while(!heap.isEmpty()) vb.addFront(heap.removeMin()); + return vb.value(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/http/HttpSendRequest.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/http/HttpSendRequest.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/http/HttpSendRequest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/http/HttpSendRequest.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,6 +3,7 @@ import org.basex.query.*; import org.basex.query.func.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.util.http.*; @@ -15,7 +16,7 @@ */ public final class HttpSendRequest extends StandardFunc { @Override - public ValueIter iter(final QueryContext qc) throws QueryException { + public Iter iter(final QueryContext qc) throws QueryException { checkCreate(qc); // get request node @@ -24,13 +25,14 @@ // get HTTP URI final byte[] href = exprs.length >= 2 ? toEmptyToken(exprs[1], qc) : null; // get parameter $bodies - ValueBuilder cache = null; + Iter iter = null; if(exprs.length == 3) { final Iter bodies = exprs[2].iter(qc); - cache = new ValueBuilder(); + final ItemList cache = new ItemList(); for(Item body; (body = bodies.next()) != null;) cache.add(body); + iter = cache.iter(); } // send HTTP request - return new HttpClient(info, qc.context.options).sendRequest(href, request, cache); + return new HttpClient(info, qc.context.options).sendRequest(href, request, iter); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/index/IndexFn.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/index/IndexFn.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/index/IndexFn.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/index/IndexFn.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,6 +8,7 @@ import org.basex.data.*; import org.basex.index.*; import org.basex.index.query.*; +import org.basex.index.value.*; import org.basex.query.*; import org.basex.query.func.*; import org.basex.query.iter.*; @@ -42,7 +43,7 @@ public static Iter entries(final Data data, final IndexEntries entries, final StandardFunc call) throws QueryException { - final Index index; + final ValueIndex index; final boolean avl; final IndexType it = entries.type(); if(it == IndexType.TEXT) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/inspect/InspectFunctions.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/inspect/InspectFunctions.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/inspect/InspectFunctions.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/inspect/InspectFunctions.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,6 +11,7 @@ import org.basex.query.expr.*; import org.basex.query.func.*; import org.basex.query.iter.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.var.*; import org.basex.util.*; @@ -24,6 +25,11 @@ public final class InspectFunctions extends StandardFunc { @Override public Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); + } + + @Override + public Value value(final QueryContext qc) throws QueryException { checkCreate(qc); // about to be updated in a future version final ArrayList old = new ArrayList<>(); @@ -45,7 +51,7 @@ final FuncItem fi = Functions.getUser(sf, qc, sf.sc, info); if(sc.mixUpdates || !fi.annotations().contains(Annotation.UPDATING)) vb.add(fi); } - return vb; + return vb.value(); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/inspect/Inspect.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/inspect/Inspect.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/inspect/Inspect.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/inspect/Inspect.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,6 +6,9 @@ import java.io.*; +import org.basex.build.*; +import org.basex.build.xml.*; +import org.basex.core.*; import org.basex.io.*; import org.basex.query.*; import org.basex.query.util.*; @@ -90,8 +93,14 @@ for(final Ann ann : anns) { final FElem annotation = elem("annotation", parent); - annotation.add("name", ann.sig.id()); - if(uri) annotation.add("uri", ann.sig.uri); + if(ann.sig != null) { + annotation.add("name", ann.sig.id()); + if(uri) annotation.add("uri", ann.sig.uri); + } else { + annotation.add("name", ann.name.string()); + if(uri) annotation.add("uri", ann.name.uri()); + } + for(final Item it : ann.args) { final FElem literal = elem("literal", annotation); literal.add("type", it.type.toString()).add(it.string(null)); @@ -121,9 +130,10 @@ * @param elem element */ public static void add(final byte[] value, final FElem elem) { + try { - final ANode node = new DBNode(new IOContent(value)); - for(final ANode n : node.children()) elem.add(n.copy()); + final Parser parser = new XMLParser(new IOContent(value), MainOptions.get(), true); + for(final ANode node : new DBNode(parser).children()) elem.add(node.copy()); } catch(final IOException ex) { // fallback: add string representation Util.debug(ex); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/inspect/XQDoc.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/inspect/XQDoc.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/inspect/XQDoc.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/inspect/XQDoc.java 2015-07-14 10:54:40.000000000 +0000 @@ -45,7 +45,7 @@ final QueryParser qp = parseQuery(io); final FElem xqdoc = new FElem(PREFIX, PREFIX, URI).declareNS(); final FElem control = elem("control", xqdoc); - elem("date", control).add(qc.initDateTime().dtm.string(info)); + elem("date", control).add(qc.initDateTime().datm.string(info)); elem("version", control).add("1.1"); final String type = module instanceof LibraryModule ? "library" : "main"; @@ -157,7 +157,8 @@ if(!anns.isEmpty()) annotation(anns, elem("annotations", parent), false); final int al = anns.size(); for(int a = 0; a < al; a++) { - final byte[] uri = anns.get(a).sig.uri; + final Ann ann = anns.get(a); + final byte[] uri = ann.sig != null ? ann.sig.uri : ann.name.uri(); if(uri.length > 0) nsCache.put(NSGlobal.prefix(uri), uri); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/JavaMapping.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/JavaMapping.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/JavaMapping.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/JavaMapping.java 2015-07-14 10:54:40.000000000 +0000 @@ -155,7 +155,7 @@ } // any other array (also nested ones) final Object[] objs = (Object[]) obj; - final ValueBuilder vb = new ValueBuilder(objs.length); + final ValueBuilder vb = new ValueBuilder(); for(final Object o : objs) vb.add(toValue(o, qc, sc)); return vb.value(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/JavaModuleFunc.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/JavaModuleFunc.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/JavaModuleFunc.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/JavaModuleFunc.java 2015-07-14 10:54:40.000000000 +0000 @@ -120,7 +120,7 @@ @Override public boolean has(final Flag f) { return f == Flag.NDT && method.getAnnotation(Deterministic.class) == null || - (f == Flag.CTX || f == Flag.FCS) && + (f == Flag.CTX || f == Flag.POS) && method.getAnnotation(FocusDependent.class) == null || super.has(f); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/json/JsonFn.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/json/JsonFn.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/json/JsonFn.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/json/JsonFn.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,7 +6,7 @@ import org.basex.query.value.item.*; /** - * Functions for parsing and serializing JSON objects. + * Function implementation. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/json/JsonParse.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/json/JsonParse.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/json/JsonParse.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/json/JsonParse.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,7 +7,7 @@ import org.basex.util.*; /** - * Functions for parsing and serializing JSON objects. + * Function implementation. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/json/JsonSerialize.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/json/JsonSerialize.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/json/JsonSerialize.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/json/JsonSerialize.java 2015-07-14 10:54:40.000000000 +0000 @@ -10,7 +10,7 @@ import org.basex.util.*; /** - * Functions for parsing and serializing JSON objects. + * Function implementation. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/map/MapForEach.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/map/MapForEach.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/map/MapForEach.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/map/MapForEach.java 2015-07-14 10:54:40.000000000 +0000 @@ -14,11 +14,11 @@ public final class MapForEach extends StandardFunc { @Override public Iter iter(final QueryContext qc) throws QueryException { - return toMap(exprs[0], qc).apply(checkArity(exprs[1], 2, qc), qc, info); + return value(qc).iter(); } @Override public Value value(final QueryContext qc) throws QueryException { - return iter(qc).value(); + return toMap(exprs[0], qc).apply(checkArity(exprs[1], 2, qc), qc, info); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/repo/RepoList.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/repo/RepoList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/repo/RepoList.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/repo/RepoList.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,6 +3,7 @@ import org.basex.io.*; import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.util.pkg.*; import org.basex.query.util.pkg.Package; import org.basex.query.value.node.*; @@ -24,8 +25,8 @@ private static final String VERSION = "version"; @Override - public Iter iter(final QueryContext qc) throws QueryException { - final NodeSeqBuilder cache = new NodeSeqBuilder(); + public BasicNodeIter iter(final QueryContext qc) throws QueryException { + final ANodeList cache = new ANodeList(); final Repo repo = qc.context.repo.reset(); for(final byte[] p : repo.pkgDict()) { if(p == null) continue; @@ -45,6 +46,6 @@ cache.add(elem); } } - return cache; + return cache.iter(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/sql/SqlExecutePrepared.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/sql/SqlExecutePrepared.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/sql/SqlExecutePrepared.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/sql/SqlExecutePrepared.java 2015-07-14 10:54:40.000000000 +0000 @@ -46,6 +46,8 @@ /** Attribute "type" of . */ private static final byte[] TYPE = token("type"); + /** Attribute "null" of . */ + private static final byte[] NULL = token("null"); @Override public Iter iter(final QueryContext qc) throws QueryException { @@ -78,7 +80,7 @@ * @return number of parameters */ private static long countParams(final ANode params) { - final AxisIter ch = params.children(); + final BasicNodeIter ch = params.children(); long n = ch.size(); if(n == -1) do ++n; while(ch.next() != null); @@ -91,14 +93,14 @@ * @param stmt prepared statement * @throws QueryException query exception */ - private void setParameters(final AxisMoreIter params, final PreparedStatement stmt) + private void setParameters(final BasicNodeIter params, final PreparedStatement stmt) throws QueryException { int i = 0; for(ANode next; (next = params.next()) != null;) { // Check name if(!next.qname().eq(Q_PARAMETER)) throw INVALIDOPTION_X.get(info, next.qname().local()); - final AxisIter attrs = next.attributes(); + final BasicNodeIter attrs = next.attributes(); byte[] paramType = null; boolean isNull = false; for(ANode attr; (attr = attrs.next()) != null;) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/StandardFunc.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/StandardFunc.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/StandardFunc.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/StandardFunc.java 2015-07-14 10:54:40.000000000 +0000 @@ -20,6 +20,7 @@ import org.basex.query.iter.*; import org.basex.query.util.*; import org.basex.query.util.collation.*; +import org.basex.query.util.list.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.map.Map; @@ -99,7 +100,7 @@ final int es = exprs.length; final Expr[] arg = new Expr[es]; for(int e = 0; e < es; e++) arg[e] = exprs[e].copy(qc, scp, vs); - return sig.get(sc, info, arg); + return copyType(sig.get(sc, info, arg)); } /** @@ -163,7 +164,7 @@ * @return expression * @throws QueryException query exception */ - protected Expr arg(final int index, final QueryContext qc) throws QueryException { + protected Expr ctxArg(final int index, final QueryContext qc) throws QueryException { return exprs.length == index ? ctxValue(qc) : exprs[index]; } @@ -373,17 +374,17 @@ /** * Caches and materializes all items of the specified iterator. * @param iter iterator - * @param vb value builder + * @param cache item list * @param qc query context * @throws QueryException query exception */ - protected final void cache(final Iter iter, final ValueBuilder vb, final QueryContext qc) + protected final void cache(final Iter iter, final ItemList cache, final QueryContext qc) throws QueryException { for(Item it; (it = iter.next()) != null;) { qc.checkStop(); if(it instanceof FItem) throw FISTRING_X.get(info, it.type); - vb.add(it.materialize(info)); + cache.add(it.materialize(info)); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/StaticFuncCall.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/StaticFuncCall.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/StaticFuncCall.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/StaticFuncCall.java 2015-07-14 10:54:40.000000000 +0000 @@ -119,8 +119,8 @@ public boolean has(final Flag flag) { // check arguments, which will be evaluated before running the function code if(super.has(flag)) return true; - // function code: position or context references will have no effect on calling code - if(flag == Flag.FCS || flag == Flag.CTX) return false; + // function code: position or context references of expression body have no effect + if(flag == Flag.POS || flag == Flag.CTX) return false; // pass on check to function code return func == null || (flag == Flag.UPD && !sc.mixUpdates ? func.updating : func.has(flag)); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/stream/StreamIsStreamable.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/stream/StreamIsStreamable.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/stream/StreamIsStreamable.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/stream/StreamIsStreamable.java 2015-07-14 10:54:40.000000000 +0000 @@ -14,7 +14,7 @@ public final class StreamIsStreamable extends StandardFunc { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final Item it = toItem(exprs[0], qc); + final Item it = toAtomItem(exprs[0], qc); return Bln.get(it instanceof StrStream || it instanceof B64Stream); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/unit/UnitAssertEquals.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/unit/UnitAssertEquals.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/unit/UnitAssertEquals.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/unit/UnitAssertEquals.java 2015-07-14 10:54:40.000000000 +0000 @@ -17,7 +17,7 @@ public final class UnitAssertEquals extends UnitFn { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final Item it = exprs.length < 3 ? null : toItem(exprs[2], qc); + final Item it = exprs.length < 3 ? null : toNodeOrAtomItem(exprs[2], qc); final Iter iter1 = qc.iter(exprs[0]), iter2 = qc.iter(exprs[1]); final Compare comp = new Compare(info); Item it1, it2; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/unit/UnitAssert.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/unit/UnitAssert.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/unit/UnitAssert.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/unit/UnitAssert.java 2015-07-14 10:54:40.000000000 +0000 @@ -13,8 +13,9 @@ public final class UnitAssert extends UnitFn { @Override public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final Item it = exprs.length < 2 ? null : toItem(exprs[1], qc); - if(exprs[0].ebv(qc, info).bool(info)) return null; + final boolean ok = exprs[0].ebv(qc, info).bool(info); + final Item it = exprs.length < 2 ? null : toNodeOrAtomItem(exprs[1], qc); + if(ok) return null; throw error(it); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/unit/UnitFail.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/unit/UnitFail.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/unit/UnitFail.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/unit/UnitFail.java 2015-07-14 10:54:40.000000000 +0000 @@ -12,6 +12,6 @@ public final class UnitFail extends UnitFn { @Override public Iter iter(final QueryContext qc) throws QueryException { - throw error(exprs.length < 1 ? null : toItem(exprs[0], qc)); + throw error(exprs.length < 1 ? null : toNodeOrAtomItem(exprs[0], qc)); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/unit/Unit.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/unit/Unit.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/unit/Unit.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/unit/Unit.java 2015-07-14 10:54:40.000000000 +0000 @@ -87,7 +87,7 @@ // find Unit annotations final AnnList anns = sf.anns; boolean xq = false; - for(final Ann ann : anns) xq |= eq(ann.sig.uri, QueryText.UNIT_URI); + for(final Ann ann : anns) xq |= ann.sig != null && eq(ann.sig.uri, QueryText.UNIT_URI); if(!xq) continue; // Unit function: diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/user/UserAlter.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/user/UserAlter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/user/UserAlter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/user/UserAlter.java 2015-07-14 10:54:40.000000000 +0000 @@ -41,14 +41,14 @@ */ private Alter(final User user, final String newname, final QueryContext qc, final InputInfo info) { - super(UpdateType.USERALTER, user, null, qc, info); + super(UpdateType.USERALTER, user, "", qc, info); this.newname = newname; } @Override public void apply() { final User old = users.get(newname); - if(old != null) users.drop(old, null); + if(old != null) users.drop(old, ""); users.alter(user, newname); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/user/UserCreate.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/user/UserCreate.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/user/UserCreate.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/user/UserCreate.java 2015-07-14 10:54:40.000000000 +0000 @@ -47,7 +47,7 @@ */ private Create(final String name, final String pw, final Perm perm, final User user, final QueryContext qc, final InputInfo info) { - super(UpdateType.USERCREATE, user, null, qc, info); + super(UpdateType.USERCREATE, user, "", qc, info); this.name = name; this.pw = pw; this.perm = perm; @@ -55,7 +55,7 @@ @Override public void apply() { - if(user != null) users.drop(user, null); + if(user != null) users.drop(user, ""); users.create(name, pw, perm); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/user/UserDrop.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/user/UserDrop.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/user/UserDrop.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/user/UserDrop.java 2015-07-14 10:54:40.000000000 +0000 @@ -19,7 +19,7 @@ public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { checkAdmin(qc); final User user = toSafeUser(0, qc); - final String db = exprs.length > 1 ? toDB(1, qc) : null; + final String db = exprs.length > 1 ? toDB(1, qc) : ""; if(user.name().equals(UserText.ADMIN)) throw USER_ADMIN.get(info); qc.resources.updates().add(new Drop(user, db, qc, ii), qc); return null; @@ -30,12 +30,13 @@ /** * Constructor. * @param user user - * @param db database (optional) + * @param pattern database (optional) * @param qc query context * @param info input info */ - private Drop(final User user, final String db, final QueryContext qc, final InputInfo info) { - super(UpdateType.USERDROP, user, db, qc, info); + private Drop(final User user, final String pattern, final QueryContext qc, + final InputInfo info) { + super(UpdateType.USERDROP, user, pattern, qc, info); } @Override @@ -43,7 +44,7 @@ boolean global = false; for(final String db : patterns) global |= db == null; if(global) { - users.drop(user, null); + users.drop(user, ""); } else { for(final String db : patterns) users.drop(user, db); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/user/UserGrant.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/user/UserGrant.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/user/UserGrant.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/user/UserGrant.java 2015-07-14 10:54:40.000000000 +0000 @@ -20,9 +20,9 @@ checkAdmin(qc); final User user = toSafeUser(0, qc); final Perm perm = toPerm(1, qc); - final String db = exprs.length > 2 ? toDB(2, qc) : null; + final String db = exprs.length > 2 ? toDB(2, qc) : ""; if(user.name().equals(UserText.ADMIN)) throw USER_ADMIN.get(info); - if(db != null && (perm == Perm.CREATE || perm == Perm.ADMIN)) + if(!db.isEmpty() && (perm == Perm.CREATE || perm == Perm.ADMIN)) throw USER_LOCAL.get(info); qc.resources.updates().add(new Grant(user, perm, db, qc, ii), qc); @@ -38,13 +38,13 @@ * Constructor. * @param user user * @param perm permission - * @param db database + * @param pattern pattern * @param qc query context * @param info input info */ - private Grant(final User user, final Perm perm, final String db, final QueryContext qc, + private Grant(final User user, final Perm perm, final String pattern, final QueryContext qc, final InputInfo info) { - super(UpdateType.USERGRANT, user, db, qc, info); + super(UpdateType.USERGRANT, user, pattern, qc, info); this.perm = perm; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/user/UserListDetails.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/user/UserListDetails.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/user/UserListDetails.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/user/UserListDetails.java 2015-07-14 10:54:40.000000000 +0000 @@ -20,6 +20,11 @@ public final class UserListDetails extends UserList { @Override public Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); + } + + @Override + public Value value(final QueryContext qc) throws QueryException { checkAdmin(qc); final User u = exprs.length > 0 ? toUser(0, qc) : null; @@ -42,11 +47,6 @@ } vb.add(user); } - return vb; - } - - @Override - public Value value(final QueryContext qc) throws QueryException { - return iter(qc).value(); + return vb.value(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/user/UserPassword.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/user/UserPassword.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/user/UserPassword.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/user/UserPassword.java 2015-07-14 10:54:40.000000000 +0000 @@ -34,7 +34,7 @@ */ private Password(final User user, final String pw, final InputInfo info, final QueryContext qc) { - super(UpdateType.USERPASSWORD, user, null, qc, info); + super(UpdateType.USERPASSWORD, user, "", qc, info); this.pw = pw; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ErrorHandler.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ErrorHandler.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ErrorHandler.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ErrorHandler.java 2015-07-14 10:54:40.000000000 +0000 @@ -13,7 +13,7 @@ * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ -final class ErrorHandler extends DefaultHandler { +public final class ErrorHandler extends DefaultHandler { /** Will contain all raised validation exception messages. */ private final TokenList exceptions = new TokenList(); @@ -59,7 +59,7 @@ * Returns the exception messages. * @return exception messages */ - TokenList getExceptions() { + public TokenList getExceptions() { return exceptions; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateDtdInfo.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateDtdInfo.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateDtdInfo.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateDtdInfo.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,7 +7,6 @@ import org.basex.io.*; import org.basex.io.serial.*; import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.xml.sax.*; @@ -36,19 +35,19 @@ */ public class ValidateDtdInfo extends ValidateFn { @Override - public Iter iter(final QueryContext qc) throws QueryException { - return value(qc).iter(); + public Value value(final QueryContext qc) throws QueryException { + return info(qc); } @Override - public Value value(final QueryContext qc) throws QueryException { + public Value info(final QueryContext qc) throws QueryException { checkCreate(qc); - return process(new Validate() { + return process(new Validation() { @Override void process(final ErrorHandler handler) throws IOException, ParserConfigurationException, SAXException, QueryException { - final Item it = toItem(exprs[0], qc); + final Item it = toNodeOrAtomItem(exprs[0], qc); SerializerOptions sp = null; // integrate doctype declaration via serialization parameters diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateDtd.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateDtd.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateDtd.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateDtd.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,13 +1,7 @@ package org.basex.query.func.validate; -import static org.basex.query.QueryError.*; - import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.value.*; -import org.basex.query.value.item.*; -import org.basex.query.value.seq.*; -import org.basex.util.*; /** * Function implementation. @@ -17,20 +11,7 @@ */ public final class ValidateDtd extends ValidateDtdInfo { @Override - public Iter iter(final QueryContext qc) throws QueryException { - final Item it = item(qc, info); - return it != null ? it.iter() : Empty.ITER; - } - - @Override public Value value(final QueryContext qc) throws QueryException { - return item(qc, null); - } - - @Override - public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final Value seq = super.value(qc); - if(seq.isEmpty()) return null; - throw BXVA_FAIL_X.get(info, seq.iter().next()); + return check(qc); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateFn.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateFn.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateFn.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateFn.java 2015-07-14 10:54:40.000000000 +0000 @@ -13,6 +13,7 @@ import org.basex.io.serial.*; import org.basex.query.*; import org.basex.query.func.*; +import org.basex.query.iter.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -28,13 +29,38 @@ * @author Marco Lettere (greedy/verbose validation) */ abstract class ValidateFn extends StandardFunc { + @Override + public final Iter iter(final QueryContext qc) throws QueryException { + return value(qc).iter(); + } + + /** + * Runs the validation process. + * @param qc query context. + * @return resulting value + * @throws QueryException query exception + */ + public final Value check(final QueryContext qc) throws QueryException { + final Value seq = info(qc); + if(seq.isEmpty()) return Empty.SEQ; + throw BXVA_FAIL_X.get(info, seq.iter().next()); + } + + /** + * Runs the validation process and returns a string sequence. + * @param qc query context. + * @return resulting value + * @throws QueryException query exception + */ + public abstract Value info(final QueryContext qc) throws QueryException; + /** * Runs the specified validator. * @param v validator code * @return string sequence with warnings and errors * @throws QueryException query exception */ - final Value process(final Validate v) throws QueryException { + final Value process(final Validation v) throws QueryException { final ErrorHandler handler = new ErrorHandler(); try { v.process(handler); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/Validate.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/Validate.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/Validate.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/Validate.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -package org.basex.query.func.validate; - -import java.io.*; - -import javax.xml.parsers.*; - -import org.basex.io.*; -import org.basex.query.*; -import org.xml.sax.*; - -/** Abstract validator class. */ -abstract class Validate { - /** Temporary file instance. */ - IOFile tmp; - - /** - * Starts the validation. - * @param h error handler - * @throws IOException I/O exception - * @throws ParserConfigurationException parser configuration exception - * @throws SAXException SAX exception - * @throws QueryException query exception - */ - abstract void process(ErrorHandler h) - throws IOException, ParserConfigurationException, SAXException, QueryException; -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateRngInfo.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateRngInfo.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateRngInfo.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateRngInfo.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,78 @@ +package org.basex.query.func.validate; + +import static org.basex.query.QueryError.*; + +import java.io.*; +import java.lang.reflect.*; + +import org.basex.io.*; +import org.basex.query.*; +import org.basex.query.value.*; +import org.basex.util.*; +import org.xml.sax.*; + +/** + * Validates a document against a RelaxNG document. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public class ValidateRngInfo extends ValidateFn { + @Override + public Value value(final QueryContext qc) throws QueryException { + return info(qc); + } + + @Override + public Value info(final QueryContext qc) throws QueryException { + checkCreate(qc); + return process(new Validation() { + @Override + void process(final ErrorHandler handler) throws IOException, SAXException, QueryException { + final IO in = read(toNodeOrAtomItem(exprs[0], qc), qc, null); + + // schema specified as string + IO schema = read(toNodeOrAtomItem(exprs[1], qc), qc, null); + tmp = createTmp(schema); + if(tmp != null) schema = tmp; + + final boolean compact = exprs.length > 2 && toBoolean(exprs[2], qc); + + try { + final Class + pmb = Class.forName("com.thaiopensource.util.PropertyMapBuilder"), + vd = Class.forName("com.thaiopensource.validate.ValidationDriver"), + vp = Class.forName("com.thaiopensource.validate.ValidateProperty"), + pi = Class.forName("com.thaiopensource.util.PropertyId"), + pm = Class.forName("com.thaiopensource.util.PropertyMap"), + sr = Class.forName("com.thaiopensource.validate.SchemaReader"), + csr = Class.forName("com.thaiopensource.validate.rng.CompactSchemaReader"); + + final Object ehInstance = vp.getField("ERROR_HANDLER").get(null); + final Object pmbInstance = pmb.newInstance(); + pi.getMethod("put", pmb, Object.class).invoke(ehInstance, pmbInstance, handler); + + final Object srInstance = compact ? csr.getMethod("getInstance").invoke(null) : null; + final Object pmInstance = pmb.getMethod("toPropertyMap").invoke(pmbInstance); + final Object vdInstance = vd.getConstructor(pm, sr).newInstance(pmInstance, srInstance); + + final Method vdLs = vd.getMethod("loadSchema", InputSource.class); + final Object loaded = vdLs.invoke(vdInstance, schema.inputSource()); + if(Boolean.TRUE.equals(loaded)) { + final Method vdV = vd.getMethod("validate", InputSource.class); + vdV.invoke(vdInstance, in.inputSource()); + } + } catch(final ClassNotFoundException ex) { + throw BXVA_RELAXNG_X.get(info); + } catch(final Exception ex) { + Throwable e = ex; + while(e.getCause() != null) { + Util.debug(e); + e = e.getCause(); + } + throw BXVA_FAIL_X.get(info, e); + } + } + }); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateRng.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateRng.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateRng.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateRng.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,17 @@ +package org.basex.query.func.validate; + +import org.basex.query.*; +import org.basex.query.value.*; + +/** + * Function implementation. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class ValidateRng extends ValidateRngInfo { + @Override + public Value value(final QueryContext qc) throws QueryException { + return check(qc); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateXsdInfo.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateXsdInfo.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateXsdInfo.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateXsdInfo.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,9 +9,7 @@ import org.basex.io.*; import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.value.*; -import org.basex.query.value.item.*; import org.xml.sax.*; /** @@ -43,26 +41,26 @@ */ public class ValidateXsdInfo extends ValidateFn { @Override - public Iter iter(final QueryContext qc) throws QueryException { - return value(qc).iter(); + public Value value(final QueryContext qc) throws QueryException { + return info(qc); } @Override - public Value value(final QueryContext qc) throws QueryException { + public Value info(final QueryContext qc) throws QueryException { checkCreate(qc); - return process(new Validate() { + return process(new Validation() { @Override void process(final ErrorHandler handler) throws IOException, SAXException, QueryException { - final IO in = read(toItem(exprs[0], qc), qc, null); + final IO in = read(toNodeOrAtomItem(exprs[0], qc), qc, null); + final SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); final Schema schema; if(exprs.length < 2) { // assume that schema declaration is included in document schema = sf.newSchema(); } else { - final Item it = toItem(exprs[1], qc); // schema specified as string - IO scio = read(it, qc, null); + IO scio = read(toNodeOrAtomItem(exprs[1], qc), qc, null); tmp = createTmp(scio); if(tmp != null) scio = tmp; schema = sf.newSchema(new URL(scio.url())); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateXsd.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateXsd.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/ValidateXsd.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/ValidateXsd.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,13 +1,7 @@ package org.basex.query.func.validate; -import static org.basex.query.QueryError.*; - import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.value.*; -import org.basex.query.value.item.*; -import org.basex.query.value.seq.*; -import org.basex.util.*; /** * Function implementation. @@ -17,20 +11,7 @@ */ public final class ValidateXsd extends ValidateXsdInfo { @Override - public Iter iter(final QueryContext qc) throws QueryException { - final Item it = item(qc, info); - return it != null ? it.iter() : Empty.ITER; - } - - @Override public Value value(final QueryContext qc) throws QueryException { - return item(qc, null); - } - - @Override - public Item item(final QueryContext qc, final InputInfo ii) throws QueryException { - final Value seq = super.value(qc); - if(seq.isEmpty()) return null; - throw BXVA_FAIL_X.get(info, seq.iter().next()); + return check(qc); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/Validation.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/Validation.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/validate/Validation.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/validate/Validation.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,26 @@ +package org.basex.query.func.validate; + +import java.io.*; + +import javax.xml.parsers.*; + +import org.basex.io.*; +import org.basex.query.*; +import org.xml.sax.*; + +/** Abstract validator class. */ +abstract class Validation { + /** Temporary file instance. */ + IOFile tmp; + + /** + * Starts the validation. + * @param h error handler + * @throws IOException I/O exception + * @throws ParserConfigurationException parser configuration exception + * @throws SAXException SAX exception + * @throws QueryException query exception + */ + abstract void process(ErrorHandler h) + throws IOException, ParserConfigurationException, SAXException, QueryException; +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/web/WebDecodeUrl.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/web/WebDecodeUrl.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/web/WebDecodeUrl.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/web/WebDecodeUrl.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,37 @@ +package org.basex.query.func.web; + +import static org.basex.query.QueryError.*; +import static org.basex.util.Token.*; + +import java.io.*; +import java.net.*; + +import org.basex.query.*; +import org.basex.query.value.item.*; +import org.basex.util.*; + +/** + * Function implementation. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class WebDecodeUrl extends WebFn { + @Override + public Str item(final QueryContext qc, final InputInfo ii) throws QueryException { + try { + final byte[] uri = toToken(exprs[0], qc); + final byte[] token = token(URLDecoder.decode(string(uri), Strings.UTF8)); + final TokenParser tp = new TokenParser(token); + while(tp.more()) { + if(!XMLToken.valid(tp.next())) throw BXWE_CODES_X.get(info, uri); + } + return Str.get(token); + } catch(final UnsupportedEncodingException ex) { + /* UTF8 is always supported */ + throw Util.notExpected(); + } catch(final IllegalArgumentException ex) { + throw BXWE_INVALID_X.get(info, ex.getLocalizedMessage()); + } + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/web/WebEncodeUrl.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/web/WebEncodeUrl.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/web/WebEncodeUrl.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/web/WebEncodeUrl.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,26 @@ +package org.basex.query.func.web; + +import java.io.*; +import java.net.*; + +import org.basex.query.*; +import org.basex.query.value.item.*; +import org.basex.util.*; + +/** + * Function implementation. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class WebEncodeUrl extends WebFn { + @Override + public Str item(final QueryContext qc, final InputInfo ii) throws QueryException { + try { + return Str.get(URLEncoder.encode(Token.string(toToken(exprs[0], qc)), Strings.UTF8)); + } catch(final UnsupportedEncodingException ex) { + /* UTF8 is always supported */ + throw Util.notExpected(); + } + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/xquery/XQueryEval.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/xquery/XQueryEval.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/xquery/XQueryEval.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/xquery/XQueryEval.java 2015-07-14 10:54:40.000000000 +0000 @@ -13,6 +13,7 @@ import org.basex.query.func.*; import org.basex.query.iter.*; import org.basex.query.util.*; +import org.basex.query.util.list.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.util.*; @@ -39,13 +40,23 @@ } @Override - public Iter iter(final QueryContext qc) throws QueryException { - return eval(qc, toToken(exprs[0], qc), null, false); + public final Iter iter(final QueryContext qc) throws QueryException { + return eval(qc).iter(); } @Override public final Value value(final QueryContext qc) throws QueryException { - return iter(qc).value(); + return eval(qc).value(); + } + + /** + * Evaluates a query. + * @param qc query context + * @return resulting value + * @throws QueryException query exception + */ + protected ItemList eval(final QueryContext qc) throws QueryException { + return eval(qc, toToken(exprs[0], qc), null, false); } /** @@ -57,13 +68,13 @@ * @return resulting value * @throws QueryException query exception */ - final ValueBuilder eval(final QueryContext qc, final byte[] qu, final String path, + final ItemList eval(final QueryContext qc, final byte[] qu, final String path, final boolean updating) throws QueryException { // bind variables and context value final HashMap bindings = toBindings(1, qc); final User user = qc.context.user(); - final Perm tmp = user.perm(null); + final Perm tmp = user.perm(""); Timer to = null; try(final QueryContext qctx = qc.proc(new QueryContext(qc))) { @@ -71,10 +82,9 @@ final Options opts = toOptions(2, Q_OPTIONS, new XQueryOptions(), qc); final Perm perm = Perm.get(opts.get(XQueryOptions.PERMISSION).toString()); if(!user.has(perm)) throw BXXQ_PERM2_X.get(info, perm); - user.perm(perm, null); + user.perm(perm, ""); // initial memory consumption: perform garbage collection and calculate usage - Performance.gc(2); final long mb = opts.get(XQueryOptions.MEMORY); if(mb != 0) { final long limit = Performance.memory() + (mb << 20); @@ -83,10 +93,7 @@ @Override public void run() { // limit reached: perform garbage collection and check again - if(Performance.memory() > limit) { - Performance.gc(1); - if(Performance.memory() > limit) qctx.stop(); - } + if(Performance.memory() > limit) qctx.stop(); } }, 500, 500); } @@ -102,7 +109,7 @@ // evaluate query try { - final StaticContext sctx = new StaticContext(qctx.context); + final StaticContext sctx = new StaticContext(qctx); for(final Entry it : bindings.entrySet()) { final String key = it.getKey(); final Value val = it.getValue(); @@ -118,9 +125,9 @@ if(qctx.updating) throw BXXQ_UPDATING.get(info); } - final ValueBuilder vb = new ValueBuilder(); - cache(qctx.iter(), vb, qctx); - return vb; + final ItemList cache = new ItemList(); + cache(qctx.iter(), cache, qctx); + return cache; } catch(final ProcException ex) { throw BXXQ_STOPPED.get(info); } catch(final QueryException ex) { @@ -129,7 +136,7 @@ } } finally { - user.perm(tmp, null); + user.perm(tmp, ""); qc.proc(null); if(to != null) to.cancel(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/xquery/XQueryInvoke.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/xquery/XQueryInvoke.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/xquery/XQueryInvoke.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/xquery/XQueryInvoke.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,7 +6,7 @@ import org.basex.io.*; import org.basex.query.*; -import org.basex.query.iter.*; +import org.basex.query.util.list.*; /** * Function implementation. @@ -16,7 +16,7 @@ */ public final class XQueryInvoke extends XQueryEval { @Override - public Iter iter(final QueryContext qc) throws QueryException { + protected ItemList eval(final QueryContext qc) throws QueryException { checkCreate(qc); final IO io = checkPath(exprs[0], qc); try { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/xquery/XQueryUpdate.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/xquery/XQueryUpdate.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/xquery/XQueryUpdate.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/xquery/XQueryUpdate.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,7 @@ package org.basex.query.func.xquery; import org.basex.query.*; -import org.basex.query.iter.*; +import org.basex.query.util.list.*; /** * Function implementation. @@ -11,7 +11,7 @@ */ public final class XQueryUpdate extends XQueryEval { @Override - public Iter iter(final QueryContext qc) throws QueryException { + protected ItemList eval(final QueryContext qc) throws QueryException { return eval(qc, toToken(exprs[0], qc), null, true); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/xslt/XsltTransform.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/xslt/XsltTransform.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/xslt/XsltTransform.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/xslt/XsltTransform.java 2015-07-14 10:54:40.000000000 +0000 @@ -72,7 +72,7 @@ * @throws QueryException query exception */ private IO read(final Expr ex, final QueryContext qc) throws QueryException { - final Item it = toItem(ex, qc); + final Item it = toNodeOrAtomItem(ex, qc); if(it instanceof ANode) { try { final IO io = new IOContent(it.serialize(SerializerOptions.get(false)).finish()); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/func/zip/ZipZipFile.java basex-8.2.3/basex-core/src/main/java/org/basex/query/func/zip/ZipZipFile.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/func/zip/ZipZipFile.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/func/zip/ZipZipFile.java 2015-07-14 10:54:40.000000000 +0000 @@ -60,18 +60,18 @@ * Adds files to the specified zip output, or copies files from the * specified file. * @param zos output stream - * @param ai axis iterator + * @param iter axis iterator * @param root root path * @param qc query context * @param zf original zip file (or {@code null}) * @throws QueryException query exception * @throws IOException I/O exception */ - final void create(final ZipOutputStream zos, final AxisIter ai, final String root, + final void create(final ZipOutputStream zos, final BasicNodeIter iter, final String root, final ZipFile zf, final QueryContext qc) throws QueryException, IOException { final byte[] data = new byte[IO.BLOCKSIZE]; - for(ANode node; (node = ai.next()) != null;) { + for(ANode node; (node = iter.next()) != null;) { // get entry type final QNm mode = node.qname(); final boolean dir = mode.eq(Q_DIR); @@ -106,7 +106,7 @@ } } else { // no source reference: the child nodes are treated as file contents - final AxisIter ch = node.children(); + final BasicNodeIter ch = node.children(); final String m = attribute(node, METHOD, false); // retrieve first child (might be null) ANode n = ch.next(); @@ -171,7 +171,7 @@ private static SerializerOptions so(final ANode node) throws BaseXException { // interpret query parameters final SerializerOptions sopts = new SerializerOptions(); - final AxisIter ati = node.attributes(); + final BasicNodeIter ati = node.attributes(); for(ANode at; (at = ati.next()) != null;) { final byte[] name = at.qname().string(); if(eq(name, NAME, SRC)) continue; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/AxisIter.java basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/AxisIter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/AxisIter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/AxisIter.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,49 +0,0 @@ -package org.basex.query.iter; - -import java.util.*; - -import org.basex.query.value.node.*; -import org.basex.util.*; - -/** - * Interface for light-weight axis iterators, throwing no exceptions. - * - * This class also implements the {@link Iterable} interface, which is why all of its - * values can also be retrieved via enhanced for(for-each) loops. Note, however, that - * using the {@link #next()} method will give you better performance. - * - * Important: to improve performance, this iterator may return the same node - * instance with updated values. If resulting nodes are to be further processed, - * they need to be finalized via {@link ANode#finish()}. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -public abstract class AxisIter extends NodeIter implements Iterable { - @Override - public abstract ANode next(); - - @Override - public final Iterator iterator() { - return new Iterator() { - /** Current node. */ - private ANode n; - - @Override - public boolean hasNext() { - n = AxisIter.this.next(); - return n != null; - } - - @Override - public ANode next() { - return n; - } - - @Override - public void remove() { - throw Util.notExpected(); - } - }; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/AxisMoreIter.java basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/AxisMoreIter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/AxisMoreIter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/AxisMoreIter.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -package org.basex.query.iter; - -import org.basex.query.value.*; -import org.basex.query.value.item.*; -import org.basex.query.value.node.*; -import org.basex.query.value.seq.*; - -/** - * Iterator interface, extending the default iterator with a {@link #more()} method. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -public abstract class AxisMoreIter extends AxisIter { - /** Empty iterator. */ - public static final AxisMoreIter EMPTY = new AxisMoreIter() { - @Override public boolean more() { return false; } - @Override public ANode next() { return null; } - @Override public Item get(final long i) { return null; } - @Override public long size() { return 0; } - @Override public Value value() { return Empty.SEQ; } - }; - - /** - * Checks if more nodes are found. - * @return temporary node - */ - public abstract boolean more(); -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/BasicNodeIter.java basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/BasicNodeIter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/BasicNodeIter.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/BasicNodeIter.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,59 @@ +package org.basex.query.iter; + +import java.util.*; + +import org.basex.query.value.*; +import org.basex.query.value.node.*; +import org.basex.query.value.seq.*; +import org.basex.util.*; + +/** + * Basic node iterator, throwing no exceptions. + * + * This class also implements the {@link Iterable} interface, which is why all of its + * values can also be retrieved via enhanced for(for-each) loops. Note, however, that + * the {@link #next()} method will give you better performance. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public abstract class BasicNodeIter extends NodeIter implements Iterable { + /** Empty iterator. */ + public static final BasicNodeIter EMPTY = new BasicNodeIter() { + @Override public ANode next() { return null; } + @Override public long size() { return 0; } + @Override public Value value() { return Empty.SEQ; } + }; + + @Override + public abstract ANode next(); + + @Override + public ANode get(final long i) { + return null; + } + + @Override + public final Iterator iterator() { + return new Iterator() { + private ANode node; + + @Override + public boolean hasNext() { + final ANode n = BasicNodeIter.this.next(); + node = n; + return n != null; + } + + @Override + public ANode next() { + return node; + } + + @Override + public void remove() { + throw Util.notExpected(); + } + }; + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/Iter.java basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/Iter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/Iter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/Iter.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,7 +4,6 @@ import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.seq.*; -import org.basex.util.*; /** * Iterator interface. @@ -51,27 +50,14 @@ */ public Value value() throws QueryException { // check if sequence is empty - Item i = next(); - if(i == null) return Empty.SEQ; + final Item i1 = next(); + if(i1 == null) return Empty.SEQ; - // if possible, allocate array with final size, and add all single items - Item[] item = new Item[Math.max(1, (int) size())]; - int s = 0; - do { - if(s == item.length) item = extend(item); - item[s++] = i; - } while((i = next()) != null); + final Item i2 = next(); + if(i2 == null) return i1; - // create final value - return Seq.get(item, s); - } - - /** - * Doubles the size of an item array. - * @param it item array - * @return resulting array - */ - static Item[] extend(final Item[] it) { - return Array.copy(it, new Item[Array.newSize(it.length)]); + final ValueBuilder vb = new ValueBuilder().add(i1).add(i2); + for(Item i; (i = next()) != null;) vb.add(i); + return vb.value(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/NodeIter.java basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/NodeIter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/NodeIter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/NodeIter.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,7 +4,7 @@ import org.basex.query.value.node.*; /** - * Node iterator interface. + * ANode iterator interface. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen @@ -12,4 +12,9 @@ public abstract class NodeIter extends Iter { @Override public abstract ANode next() throws QueryException; + + @Override + public ANode get(final long i) throws QueryException { + return null; + } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/NodeSeqBuilder.java basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/NodeSeqBuilder.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/NodeSeqBuilder.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/NodeSeqBuilder.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,287 +0,0 @@ -package org.basex.query.iter; - -import java.util.*; - -import org.basex.data.*; -import org.basex.query.value.*; -import org.basex.query.value.node.*; -import org.basex.query.value.seq.*; -import org.basex.query.value.type.*; -import org.basex.util.*; - -/** - * This class can be used to build new node sequences. - * At the same time, it serves as an iterator. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -public final class NodeSeqBuilder extends AxisIter { - /** Node container. */ - public ANode[] nodes; - /** Number of nodes. */ - private int size; - /** Current iterator position. */ - private int pos = -1; - /** Sort flag. */ - private boolean sort; - /** Check incoming nodes for potential duplicates and unsorted entries. */ - private boolean check; - - /** - * Constructor. - */ - public NodeSeqBuilder() { - nodes = new ANode[1]; - } - - /** - * Lightweight constructor, assigning the specified array of sorted nodes. - * @param nodes node array - * @param size size - */ - public NodeSeqBuilder(final ANode[] nodes, final int size) { - this.nodes = nodes; - this.size = size; - } - - /** - * Checks all nodes for potential duplicates and their orderedness. - * @return self reference - */ - public NodeSeqBuilder check() { - check = true; - return this; - } - - /** - * Returns the specified node. - * @param i node offset - * @return node - */ - public ANode get(final int i) { - return nodes[i]; - } - - /** - * Deletes a value at the specified position. - * @param p deletion position - */ - public void delete(final int p) { - Array.move(nodes, p + 1, -1, --size - p); - } - - /** - * Adds a node. - * @param n node to be added - */ - public void add(final ANode n) { - if(size == nodes.length) nodes = Array.copy(nodes, new ANode[Array.newSize(size)]); - if(check && !sort && size != 0) sort = nodes[size - 1].diff(n) > 0; - nodes[size++] = n; - } - - @Override - public ANode next() { - if(check) sort(sort); - return ++pos < size ? nodes[pos] : null; - } - - @Override - public ANode get(final long i) { - if(check) sort(sort); - return i < size ? nodes[(int) i] : null; - } - - @Override - public long size() { - if(check) sort(sort); - return size; - } - - /** - * Sets a new item size. - * @param s size - */ - public void size(final int s) { - size = s; - } - - @Override - public Value value() { - if(check) sort(sort); - return Seq.get(nodes, size, NodeType.NOD); - } - - /** - * Checks if binary search can be applied to this iterator, i.e. - * if all nodes are {@link DBNode} references and refer to the same database. - * @return result of check - */ - public boolean dbnodes() { - if(check) sort(sort); - - final Data data = size > 0 ? nodes[0].data() : null; - if(data == null) return false; - for(int s = 1; s < size; ++s) if(data != nodes[s].data()) return false; - return true; - } - - /** - * Checks if the iterator contains a database node with the specified pre value. - * @param n node to be found - * @param db indicates if all nodes are sorted {@link DBNode} references - * @return position, or {@code -1} - */ - public int indexOf(final ANode n, final boolean db) { - if(db) return n instanceof DBNode ? Math.max(binarySearch((DBNode) n, 0, size), -1) : -1; - final long sz = size(); - for(int s = 0; s < sz; ++s) if(nodes[s].is(n)) return s; - return -1; - } - - /** - * Performs a binary search on the given range of this sequence iterator, - * assuming that all nodes are {@link DBNode}s from the same {@link Data} - * instance (i.e., {@link #dbnodes()} returns {@code true}). - * @param n node to find - * @param start start of the search interval - * @param length length of the search interval - * @return position of the item or {@code -insertPosition - 1} if not found - */ - public int binarySearch(final DBNode n, final int start, final int length) { - if(size == 0 || n.data != nodes[0].data()) return -start - 1; - int l = start, r = start + length - 1; - while(l <= r) { - final int m = l + r >>> 1; - final int npre = ((DBNode) nodes[m]).pre; - if(npre == n.pre) return m; - if(npre < n.pre) l = m + 1; - else r = m - 1; - } - return -(l + 1); - } - - /** - * Sorts the nodes if necessary. - * @return self reference - */ - public NodeSeqBuilder sort() { - if(check) sort(sort); - return this; - } - - /** - * Sorts the nodes. - * @param force force sort - */ - private void sort(final boolean force) { - check = false; - if(size > 1) { - // sort arrays and remove duplicates - if(force) sort(0, size); - - // remove duplicates and merge scores - int i = 1; - for(int j = 1; j < size; ++j) { - while(j < size && nodes[i - 1].is(nodes[j])) j++; - if(j < size) nodes[i++] = nodes[j]; - } - size = i; - } - } - - /** - * Recursively sorts the specified items via QuickSort - * (derived from Java's sort algorithms). - * @param s start position - * @param e end position - */ - private void sort(final int s, final int e) { - if(e < 7) { - for(int i = s; i < e + s; ++i) - for(int j = i; j > s && nodes[j - 1].diff(nodes[j]) > 0; j--) s(j, j - 1); - return; - } - - int m = s + (e >> 1); - if(e > 7) { - int l = s; - int n = s + e - 1; - if(e > 40) { - final int k = e >>> 3; - l = m(l, l + k, l + (k << 1)); - m = m(m - k, m, m + k); - n = m(n - (k << 1), n - k, n); - } - m = m(l, m, n); - } - final ANode v = nodes[m]; - - int a = s, b = a, c = s + e - 1, d = c; - while(true) { - while(b <= c) { - final int h = nodes[b].diff(v); - if(h > 0) break; - if(h == 0) s(a++, b); - ++b; - } - while(c >= b) { - final int h = nodes[c].diff(v); - if(h < 0) break; - if(h == 0) s(c, d--); - --c; - } - if(b > c) break; - s(b++, c--); - } - - final int n = s + e; - int k = Math.min(a - s, b - a); - s(s, b - k, k); - k = Math.min(d - c, n - d - 1); - s(b, n - k, k); - - if((k = b - a) > 1) sort(s, k); - if((k = d - c) > 1) sort(n - k, k); - } - - /** - * Swaps x[a .. (a+n-1)] with x[b .. (b+n-1)]. - * @param a first offset - * @param b second offset - * @param n number of values - */ - private void s(final int a, final int b, final int n) { - for(int i = 0; i < n; ++i) s(a + i, b + i); - } - - /** - * Returns the index of the median of the three indexed integers. - * @param a first offset - * @param b second offset - * @param c thirst offset - * @return median - */ - private int m(final int a, final int b, final int c) { - return nodes[a].diff(nodes[b]) < 0 ? - nodes[b].diff(nodes[c]) < 0 ? b : nodes[a].diff(nodes[c]) < 0 ? c : a : - nodes[b].diff(nodes[c]) > 0 ? b : nodes[a].diff(nodes[c]) > 0 ? c : a; - } - - /** - * Swaps two entries. - * @param a first position - * @param b second position - */ - private void s(final int a, final int b) { - final ANode tmp = nodes[a]; - nodes[a] = nodes[b]; - nodes[b] = tmp; - } - - @Override - public String toString() { - return Util.className(this) + Arrays.toString(Arrays.copyOf(nodes, size)); - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/ValueBuilder.java basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/ValueBuilder.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/iter/ValueBuilder.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/iter/ValueBuilder.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,175 +0,0 @@ -package org.basex.query.iter; - -import java.io.*; -import java.util.*; - -import org.basex.data.*; -import org.basex.io.out.*; -import org.basex.io.serial.*; -import org.basex.query.value.*; -import org.basex.query.value.array.Array; -import org.basex.query.value.item.*; -import org.basex.query.value.seq.*; -import org.basex.util.*; - -/** - * This class can be used to build new sequences. - * At the same time, it may serve as an iterator. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -public final class ValueBuilder extends ValueIter implements Result { - /** Item container. */ - private Item[] items; - /** Number of items. */ - private int size; - /** Current iterator position. */ - private int pos = -1; - - /** - * Constructor. - */ - public ValueBuilder() { - this(1); - } - - /** - * Constructor. - * @param capacity initial capacity - */ - public ValueBuilder(final int capacity) { - items = new Item[capacity]; - } - - /** - * Constructor. - * @param items initial items - * @param size initial size - */ - public ValueBuilder(final Item[] items, final int size) { - this.items = items; - this.size = size; - } - - /** - * Adds the contents of a value. - * @param value value to be added - * @return self reference - */ - public ValueBuilder add(final Value value) { - if(value instanceof Item) return add((Item) value); - - final int s = size; - Item[] tmp = items; - for(final long sz = value.size(); tmp.length - s < sz;) tmp = extend(tmp); - size = s + value.writeTo(tmp, s); - items = tmp; - return this; - } - - /** - * Adds a single item. - * @param item item to be added - * @return self reference - */ - public ValueBuilder add(final Item item) { - final int s = size; - Item[] tmp = items; - if(s == tmp.length) tmp = extend(tmp); - tmp[s] = item; - size = s + 1; - items = tmp; - return this; - } - - /** - * Adds flattened arrays. - * @param it current item - */ - public void addFlattened(final Item it) { - if(it instanceof Array) { - final Iterator iter = ((Array) it).members(); - while(iter.hasNext()) { - for(final Item i : iter.next()) addFlattened(i); - } - } else { - add(it); - } - } - - @Override - public void serialize(final Serializer ser) throws IOException { - for(int c = 0; c < size && !ser.finished(); ++c) serialize(ser, c); - } - - @Override - public void serialize(final Serializer ser, final int n) throws IOException { - ser.serialize(items[n]); - } - - @Override - public Item next() { - return ++pos < size ? items[pos] : null; - } - - /** - * Sets the iterator size. - * @param s size - */ - public void size(final int s) { - size = s; - } - - @Override - public long size() { - return size; - } - - /** - * Returns the internal item container. - * @return items - */ - public Item[] items() { - return items; - } - - @Override - public Item get(final long i) { - return items[(int) i]; - } - - /** - * Sets an item to the specified position. - * @param i index - * @param item item to be set - */ - public void set(final int i, final Item item) { - items[i] = item; - } - - /** - * Returns the cached items as value. - * @return sequence (internal representation!) - */ - @Override - public Value value() { - return Seq.get(items, size); - } - - @Override - public String serialize() throws IOException { - final ArrayOutput ao = new ArrayOutput(); - serialize(Serializer.get(ao)); - return ao.toString(); - } - - @Override - public String toString() { - try { - return serialize(); - } catch(final IOException ex) { - throw Util.notExpected(ex); - } - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/MainModule.java basex-8.2.3/basex-core/src/main/java/org/basex/query/MainModule.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/MainModule.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/MainModule.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,6 +8,7 @@ import org.basex.query.func.*; import org.basex.query.iter.*; import org.basex.query.util.*; +import org.basex.query.util.list.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.query.value.type.*; @@ -82,17 +83,12 @@ * @return result * @throws QueryException evaluation exception */ - ValueBuilder cache(final QueryContext qc) throws QueryException { + ItemList cache(final QueryContext qc) throws QueryException { final int fp = scope.enter(qc); try { final Iter iter = expr.iter(qc); - final ValueBuilder cache; - if(iter instanceof ValueBuilder) { - cache = (ValueBuilder) iter; - } else { - cache = new ValueBuilder(Math.max(1, (int) iter.size())); - for(Item it; (it = iter.next()) != null;) cache.add(it); - } + final ItemList cache = new ItemList(Math.max(1, (int) iter.size())); + for(Item it; (it = iter.next()) != null;) cache.add(it); if(declType != null) declType.treat(cache.value(), info); return cache; @@ -108,7 +104,7 @@ * @throws QueryException query exception */ Iter iter(final QueryContext qc) throws QueryException { - if(declType != null) return cache(qc); + if(declType != null) return cache(qc).iter(); final int fp = scope.enter(qc); final Iter iter = expr.iter(qc); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/QueryContext.java basex-8.2.3/basex-core/src/main/java/org/basex/query/QueryContext.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/QueryContext.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/QueryContext.java 2015-07-14 10:54:40.000000000 +0000 @@ -26,6 +26,8 @@ import org.basex.query.iter.*; import org.basex.query.up.*; import org.basex.query.util.collation.*; +import org.basex.query.util.ft.*; +import org.basex.query.util.list.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -99,7 +101,7 @@ /** Current Date. */ public Item date; /** Current DateTime. */ - public Item dtm; + public Item datm; /** Current Time. */ public Item time; /** Current timezone. */ @@ -107,9 +109,9 @@ /** Current nanoseconds. */ public long nano; - /** Strings to lock defined by lock:read option. */ + /** Strings to lock defined by read-lock option. */ public final StringList readLocks = new StringList(0); - /** Strings to lock defined by lock:write option. */ + /** Strings to lock defined by write-lock option. */ public final StringList writeLocks = new StringList(0); /** Number of successive tail calls. */ @@ -131,15 +133,18 @@ /** Stack of module files that are currently parsed. */ final TokenList modStack = new TokenList(); - /** Serializer options. */ - SerializerOptions serialOpts; /** Initial context value. */ MainModule ctxItem; /** Root expression of the query. */ public MainModule root; - /** Compilation flag. */ + /** Serialization parameters. */ + private SerializerOptions serParams; + /** Indicates if the default serialization parameters are used. */ + public boolean defaultOutput; + + /** Indicates if the query has been compiled. */ private boolean compiled; /** Indicates if the query context has been closed. */ private boolean closed; @@ -298,7 +303,8 @@ // cache the initial context nodes final DBNodes nodes = context.current(); if(nodes != null) { - if(!context.perm(Perm.READ, nodes.data.meta.name)) throw BASX_PERM_X.get(null, Perm.READ); + if(!context.perm(Perm.READ, nodes.data().meta.name)) + throw BASX_PERM_X.get(null, Perm.READ); value = resources.compile(nodes); } } @@ -343,13 +349,13 @@ if(!updating) return root.iter(this); // cache results - ValueBuilder cache = root.cache(this); + ItemList cache = root.cache(this); final Updates updates = resources.updates; if(updates != null) { // if parent context exists, updates will be performed by main context if(qcParent == null) { - final ValueBuilder output = resources.output; + final ItemList output = resources.output; // copy nodes that will be affected by an update operation final HashSet datas = updates.prepare(this); @@ -367,7 +373,7 @@ } } } - return cache; + return cache.iter(); } catch(final StackOverflowError ex) { Util.debug(ex); @@ -381,7 +387,7 @@ * @param datas data references * @param dbs database names */ - private void copy(final ValueBuilder cache, final HashSet datas, final StringList dbs) { + private void copy(final ItemList cache, final HashSet datas, final StringList dbs) { final long cs = cache.size(); for(int c = 0; c < cs; c++) { final Item it = cache.get(c); @@ -427,6 +433,7 @@ public void databases(final LockResult lr) { lr.read.add(readLocks); lr.write.add(writeLocks); + // use global locking if referenced databases cannot be statically determined if(root == null || !root.databases(lr, this) || ctxItem != null && !ctxItem.databases(lr, this)) { if(updating) lr.writeAll = true; @@ -526,12 +533,15 @@ } /** - * Returns the query-specific or global serialization parameters. + * Returns query-specific or default serialization parameters. * @return serialization parameters */ public SerializerOptions serParams() { - return serialOpts != null ? serialOpts : - new SerializerOptions(context.options.get(MainOptions.SERIALIZER)); + if(serParams == null) { + serParams = new SerializerOptions(context.options.get(MainOptions.SERIALIZER)); + defaultOutput = root != null; + } + return serParams; } /** @@ -544,7 +554,7 @@ } /** - * Sets full-text options. + * Assigns full-text options. * @param opt full-text options */ public void ftOpt(final FTOpt opt) { @@ -552,6 +562,24 @@ } /** + * Creates and returns an XML query plan (expression tree) for this query. + * @return query plan + */ + public FElem plan() { + // only show root node if functions or variables exist + final FElem e = new FElem(QueryText.PLAN); + e.add(QueryText.COMPILED, token(compiled)); + if(root != null) { + for(final StaticScope scp : QueryCompiler.usedDecls(root)) scp.plan(e); + root.plan(e); + } else { + funcs.plan(e); + vars.plan(e); + } + return e; + } + + /** * Indicates that the query contains any updating expressions. */ public void updating() { @@ -592,63 +620,48 @@ // CLASS METHODS ====================================================================== /** - * Evaluates the expression with the specified context set. + * This function is called by the GUI; use {@link #iter()} instead. + * Caches and returns the result of the specified query. If all nodes are of the same database + * instance, the returned value will be of type {@link DBNodes}. + * @param max maximum number of results to cache (negative: return all values) * @return resulting value * @throws QueryException query exception */ - Result execute() throws QueryException { - // limit number of hits to be returned and displayed - int max = context.options.get(MainOptions.MAXHITS); - if(max < 0) max = Integer.MAX_VALUE; + Value cache(final int max) throws QueryException { + final int mx = max >= 0 ? max : Integer.MAX_VALUE; // evaluates the query final Iter ir = iter(); - final ValueBuilder vb = new ValueBuilder(); + final ItemList cache; Item it; // check if all results belong to the database of the input context final Data data = resources.globalData(); - if(serialOpts == null && data != null) { + if(defaultOutput && data != null) { final IntList pres = new IntList(); - while((it = ir.next()) != null) { + while((it = ir.next()) != null && it.data() == data && pres.size() < mx) { checkStop(); - if(it.data() != data) break; - if(pres.size() < max) pres.add(((DBNode) it).pre); + pres.add(((DBNode) it).pre()); } + // all results processed: return compact node sequence final int ps = pres.size(); - // all nodes have been processed: return compact node sequence - if(it == null || ps == max) return ps == 0 ? vb : new DBNodes(data, ftPosData, pres.finish()); + if(it == null || ps == mx) return new DBNodes(data, pres.finish()).ftpos(ftPosData); // otherwise, add nodes to standard iterator - for(int p = 0; p < ps; ++p) vb.add(new DBNode(data, pres.get(p))); - vb.add(it); + cache = new ItemList(); + for(int p = 0; p < ps; p++) cache.add(new DBNode(data, pres.get(p))); + cache.add(it); + } else { + cache = new ItemList(); } // use standard iterator - while((it = ir.next()) != null) { + while((it = ir.next()) != null && cache.size() < mx) { checkStop(); - if(vb.size() < max) vb.add(it.materialize(null)); + cache.add(it.materialize(null)); } - return vb; - } - - /** - * Recursively builds a query plan. - * @return resulting node - */ - public FElem plan() { - // only show root node if functions or variables exist - final FElem e = new FElem(QueryText.PLAN); - e.add(QueryText.COMPILED, token(compiled)); - if(root != null) { - for(final StaticScope scp : QueryCompiler.usedDecls(root)) scp.plan(e); - root.plan(e); - } else { - funcs.plan(e); - vars.plan(e); - } - return e; + return cache.value(); } // PRIVATE METHODS ==================================================================== @@ -662,7 +675,7 @@ * @throws QueryException query exception */ private Value cast(final Object val, final String type) throws QueryException { - final StaticContext sc = root != null ? root.sc : new StaticContext(context); + final StaticContext sc = root != null ? root.sc : new StaticContext(this); // String input Object vl = val; @@ -679,7 +692,7 @@ // sub types overriding the global value (value \2 type) if(string.indexOf('\2') != -1) { - final ValueBuilder vb = new ValueBuilder(strings.size()); + final ValueBuilder vb = new ValueBuilder(); for(final String str : strings) { final int i = str.indexOf('\2'); final String s = i == -1 ? str : str.substring(0, i); @@ -732,7 +745,7 @@ if(vl instanceof Item) return tp.cast((Item) vl, this, sc, null); // cast sequence final Value v = (Value) vl; - final ValueBuilder seq = new ValueBuilder((int) v.size()); + final ValueBuilder seq = new ValueBuilder(); for(final Item i : v) seq.add(tp.cast(i, this, sc, null)); return seq.value(); } @@ -740,7 +753,7 @@ if(vl instanceof String[]) { // cast string array final String[] strings = (String[]) vl; - final ValueBuilder seq = new ValueBuilder(strings.length); + final ValueBuilder seq = new ValueBuilder(); for(final String s : strings) seq.add(tp.cast(s, this, sc, null)); return seq.value(); } @@ -806,15 +819,15 @@ */ public QueryContext initDateTime() throws QueryException { if(time == null) { - final Date d = Calendar.getInstance().getTime(); - final String zon = DateTime.format(d, DateTime.ZONE); - final String ymd = DateTime.format(d, DateTime.DATE); - final String hms = DateTime.format(d, DateTime.TIME); - final String zn = zon.substring(0, 3) + ':' + zon.substring(3); - time = new Tim(token(hms + zn), null); - date = new Dat(token(ymd + zn), null); - dtm = new Dtm(token(ymd + 'T' + hms + zn), null); - zone = new DTDur(Strings.toInt(zon.substring(0, 3)), Strings.toInt(zon.substring(3))); + final Date dt = Calendar.getInstance().getTime(); + final String ymd = DateTime.format(dt, DateTime.DATE); + final String hms = DateTime.format(dt, DateTime.TIME); + final String zon = DateTime.format(dt, DateTime.ZONE); + final String znm = zon.substring(0, 3), zns = zon.substring(3); + time = new Tim(token(hms + znm + ':' + zns), null); + date = new Dat(token(ymd + znm + ':' + zns), null); + datm = new Dtm(token(ymd + 'T' + hms + znm + ':' + zns), null); + zone = new DTDur(Strings.toInt(znm), Strings.toInt(zns)); nano = System.nanoTime(); } return this; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/QueryError.java basex-8.2.3/basex-core/src/main/java/org/basex/query/QueryError.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/QueryError.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/QueryError.java 2015-07-14 10:54:40.000000000 +0000 @@ -50,9 +50,11 @@ // Admin module /** FODC0001. */ - BXCA_TODAY(FOAD, 1, "Today's log file cannot be deleted."), + BXAD_TODAY(FOAD, 1, "Today's log file cannot be deleted."), /** FODC0002. */ - BXCA_DELETE_X(FOAD, 2, "Log file could not be deleted: %."), + BXAD_DELETE_X(FOAD, 2, "Log file could not be deleted: %."), + /** FODC0003. */ + BXAD_TYPE_X(FOAD, 3, "Log type must consist of uppercase letters: \"%\"."), // Client module @@ -109,8 +111,6 @@ BXDB_RENAME_X(BXDB, 8, "Invalid target path: %."), /** BXDB0009. */ BXDB_RANGE_X_X_X(BXDB, 9, "Database '%' has no node with % value %."), - /** BXDB0010. */ - BXDB_EVENT_X(BXDB, 10, "Event '%' is unknown."), /** BXDB0011. */ BXDB_NAME_X(BXDB, 11, "Invalid database name '%'."), /** BXDB0012. */ @@ -155,6 +155,8 @@ BXJS_PARSEML_X(BXJS, 1, "JsonML parser: %."), /** BXJS0002. */ BXJS_SERIAL_X(BXJS, 2, "JSON serializer: %."), + /** BXJS0003. */ + BXJS_INVALID_X(BXJS, 1, "'%':'%' is not supported by the target format."), // Process module @@ -220,9 +222,18 @@ // Validation module /** BXVA0001. */ - BXVA_FAIL_X(BXVA, 1, "Validation failed. %"), + BXVA_FAIL_X(BXVA, 1, "Validation failed: %"), /** BXVA0002. */ - BXVA_START_X(BXVA, 2, "Validation could not be started. %"), + BXVA_START_X(BXVA, 2, "Validation could not be started: %"), + /** BXVA0003. */ + BXVA_RELAXNG_X(BXVA, 3, "RelaxNG validation is not available."), + + // Web module + + /** BXWE0001. */ + BXWE_INVALID_X(BXWE, 2, "%."), + /** BXWE0002. */ + BXWE_CODES_X(BXWE, 2, "URL contains invalid characters: %"), // XQuery module @@ -363,15 +374,17 @@ CX_SIGTYPINV(CX, 28, "Signature type is not supported."), /** File error. */ - FILE_NOT_FOUND_X(FILE, "not-found", "File '%' does not exist."), + FILE_NOT_FOUND_X(FILE, "not-found", "'%' does not exist."), /** File error. */ - FILE_EXISTS_X(FILE, "exists", "File '%' already exists."), + FILE_EXISTS_X(FILE, "exists", "'%' already exists."), /** File error. */ - FILE_NO_DIR_X(FILE, "no-dir", "Path '%' is no directory."), + FILE_NO_DIR_X(FILE, "no-dir", "'%' is no directory."), /** File error. */ - FILE_IS_DIR_X(FILE, "is-dir", "Path '%' is a directory."), + FILE_IS_DIR_X(FILE, "is-dir", "'%' is a directory."), /** File error. */ - FILE_ID_DIR2_X(FILE, "is-dir", "Path '%' is a non-empty directory."), + FILE_ID_DIR2_X(FILE, "is-dir", "'%' is a non-empty directory."), + /** File error. */ + FILE_IS_RELATIVE_X(FILE, "is-relative", "Base directory is relative: '%'."), /** File error. */ FILE_UNKNOWN_ENCODING_X(FILE, "unknown-encoding", "Unknown encoding '%'."), /** File error. */ @@ -522,6 +535,10 @@ JSON_OPT_X(FOJS, 5, "%"), /** FOJS0005. */ JSON_FUNC_OPT_X_X(FOJS, 5, "% expected, % found."), + /** FOJS0006. */ + JSON_INVALID_X(FOJS, 6, "%"), + /** FOJS0007. */ + JSON_ESCAPE_X(FOJS, 7, "Invalid escape sequence: %."), /** FOMP0001. */ MAP_TZ(FOMP, 1, "Map cannot contain keys with and without timezone."), @@ -1133,7 +1150,7 @@ /** XQST0097. */ INVDECZERO_X(XQST, 97, "Zero-digit property must be Unicode digit with value zero: '%'."), /** XQST0098. */ - DUPLDECFORM_X(XQST, 98, "Duplicate use of decimal-format '%'."), + DUPLDECFORM_X(XQST, 98, "Clash of decimal format properties: '%'."), /** XQST0099. */ DUPLITEM(XQST, 99, "Duplicate declaration of context value."), /** XQST0103. */ @@ -1345,6 +1362,7 @@ /** BXSL Error type. */ BXSL(BXERR_PREFIX, BXERRORS_URI), /** BXSQ Error type. */ BXSQ(BXERR_PREFIX, BXERRORS_URI), /** BXVA Error type. */ BXVA(BXERR_PREFIX, BXERRORS_URI), + /** BXWE Error type. */ BXWE(BXERR_PREFIX, BXERRORS_URI), /** BXXQ Error type. */ BXXQ(BXERR_PREFIX, BXERRORS_URI), /** HASH Error type. */ HASH(BXERR_PREFIX, BXERRORS_URI), /** UNIT Error type. */ UNIT(UNIT_PREFIX, UNIT_URI), diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/QueryParser.java basex-8.2.3/basex-core/src/main/java/org/basex/query/QueryParser.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/QueryParser.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/QueryParser.java 2015-07-14 10:54:40.000000000 +0000 @@ -21,7 +21,7 @@ import org.basex.query.expr.CmpN.OpN; import org.basex.query.expr.CmpV.OpV; import org.basex.query.expr.Expr.Flag; -import org.basex.query.expr.Context; +import org.basex.query.expr.ContextValue; import org.basex.query.expr.List; import org.basex.query.expr.constr.*; import org.basex.query.expr.ft.*; @@ -34,7 +34,6 @@ import org.basex.query.expr.path.Test.Kind; import org.basex.query.func.*; import org.basex.query.func.fn.*; -import org.basex.query.iter.*; import org.basex.query.up.expr.*; import org.basex.query.util.*; import org.basex.query.util.collation.*; @@ -134,7 +133,7 @@ // set path to query file final MainOptions opts = qc.context.options; final String bi = path != null ? path : opts.get(MainOptions.QUERYPATH); - final StaticContext sctx = sc != null ? sc : new StaticContext(qc.context); + final StaticContext sctx = sc != null ? sc : new StaticContext(qc); if(!bi.isEmpty()) sctx.baseURI(bi); this.sc = sctx; @@ -412,41 +411,44 @@ final InputInfo info = info(); final QNm name = eQName(QNAME_X, XQ_URI); - final ValueBuilder vb = new ValueBuilder(); + final ItemList items = new ItemList(); if(wsConsumeWs(PAREN1)) { do { final Expr ex = literal(); if(!(ex instanceof Item)) throw error(ANNVALUE); - vb.add((Item) ex); + items.add((Item) ex); } while(wsConsumeWs(COMMA)); wsCheck(PAREN2); } skipWs(); final Annotation sig = Annotation.get(name); + // annotation is not statically known... if(sig == null) { // reject unknown annotations with pre-defined namespaces, ignore others final byte[] uri = name.uri(); - if(NSGlobal.prefix(uri).length == 0 || eq(uri, LOCAL_URI, ERROR_URI)) continue; - throw (NSGlobal.reserved(uri) ? ANNWHICH_X_X : BASX_ANNOT_X_X).get( - info, '%', name.string()); - } - // check if annotation is specified more than once - if(sig.single && anns.contains(sig)) throw BASX_TWICE_X_X.get(info, '%', sig.id()); - - final long arity = vb.size(); - if(arity < sig.minMax[0] || arity > sig.minMax[1]) - throw BASX_ANNNUM_X_X_X.get(info, sig, arity, arity == 1 ? "" : "s"); - final int al = sig.args.length; - for(int a = 0; a < arity; a++) { - final SeqType st = sig.args[Math.min(al - 1, a)]; - final Item it = vb.get(a); - if(!st.instance(it)) throw BASX_ANNTYPE_X_X_X.get(info, sig, st, it.seqType()); - } - - final Item[] args = new Item[(int) vb.size()]; - vb.value().writeTo(args, 0); - anns.add(new Ann(info, sig, args)); + if(NSGlobal.prefix(uri).length != 0 && !eq(uri, LOCAL_URI, ERROR_URI)) { + throw (NSGlobal.reserved(uri) ? ANNWHICH_X_X : BASX_ANNOT_X_X).get( + info, '%', name.string()); + } + anns.add(new Ann(info, name, items.finish())); + + } else { + // check if annotation is specified more than once + if(sig.single && anns.contains(sig)) throw BASX_TWICE_X_X.get(info, '%', sig.id()); + + final long arity = items.size(); + if(arity < sig.minMax[0] || arity > sig.minMax[1]) + throw BASX_ANNNUM_X_X_X.get(info, sig, arity, arity == 1 ? "" : "s"); + final int al = sig.args.length; + for(int a = 0; a < arity; a++) { + final SeqType st = sig.args[Math.min(al - 1, a)]; + final Item it = items.get(a); + if(!st.instance(it)) throw BASX_ANNTYPE_X_X_X.get(info, sig, st, it.seqType()); + } + + anns.add(new Ann(info, sig, items.finish())); + } } else { break; } @@ -526,11 +528,9 @@ // output declaration if(module != null) throw error(MODOUT); - if(qc.serialOpts == null) { - qc.serialOpts = new SerializerOptions(qc.context.options.get(MainOptions.SERIALIZER)); - } + final SerializerOptions sopts = qc.serParams(); if(!decl.add("S " + name)) throw error(OUTDUPL_X, name); - qc.serialOpts.parse(name, val, sc, info()); + sopts.parse(name, val, sc, info()); } else if(eq(qnm.uri(), XQ_URI)) { throw error(DECLOPTION_X, qnm); @@ -772,7 +772,7 @@ */ public void module(final byte[] path, final byte[] uri) throws QueryException { // get absolute path - final IO io = sc.io(string(path)); + final IO io = sc.resolve(string(path), string(uri)); final byte[] p = token(io.path()); // check if module has already been parsed @@ -793,7 +793,7 @@ } qc.modStack.push(p); - final StaticContext sub = new StaticContext(qc.context); + final StaticContext sub = new StaticContext(qc); final LibraryModule lib = new QueryParser(qu, io.path(), qc, sub).parseLibrary(false); final byte[] muri = lib.name.uri(); @@ -1335,6 +1335,7 @@ */ private Expr switchh() throws QueryException { if(!wsConsumeWs(SWITCH, PAREN1, TYPEPAR)) return null; + final InputInfo ii = info(); wsCheck(PAREN1); final Expr expr = check(expr(), NOSWITCH); SwitchCase[] exprs = { }; @@ -1355,7 +1356,7 @@ exprs = Array.add(exprs, new SwitchCase(info(), cases.finish())); } while(cases.size() != 1); - return new Switch(info(), expr, exprs); + return new Switch(ii, expr, exprs); } /** @@ -1365,6 +1366,7 @@ */ private Expr typeswitch() throws QueryException { if(!wsConsumeWs(TYPESWITCH, PAREN1, TYPEPAR)) return null; + final InputInfo ii = info(); wsCheck(PAREN1); final Expr ts = check(expr(), NOTYPESWITCH); wsCheck(PAREN2); @@ -1396,7 +1398,7 @@ closeSubScope(s); } while(cs); if(cases.length == 1) throw error(NOTYPESWITCH); - return new TypeSwitch(info(), ts, cases); + return new TypeSwitch(ii, ts, cases); } /** @@ -1406,6 +1408,7 @@ */ private Expr iff() throws QueryException { if(!wsConsumeWs(IF, PAREN1, IFPAR)) return null; + final InputInfo ii = info(); wsCheck(PAREN1); final Expr iff = check(expr(), NOIF); wsCheck(PAREN2); @@ -1413,7 +1416,7 @@ final Expr thn = check(single(), NOIF); if(!wsConsumeWs(ELSE)) throw error(NOIF); final Expr els = check(single(), NOIF); - return new If(info(), iff, thn, els); + return new If(ii, iff, thn, els); } /** @@ -1425,9 +1428,10 @@ final Expr e = and(); if(!wsConsumeWs(OR)) return e; + final InputInfo ii = info(); final ExprList el = new ExprList(2).add(e); do add(el, and()); while(wsConsumeWs(OR)); - return new Or(info(), el.finish()); + return new Or(ii, el.finish()); } /** @@ -1439,9 +1443,10 @@ final Expr e = update(); if(!wsConsumeWs(AND)) return e; + final InputInfo ii = info(); final ExprList el = new ExprList(2).add(e); do add(el, update()); while(wsConsumeWs(AND)); - return new And(info(), el.finish()); + return new And(ii, el.finish()); } /** @@ -1775,13 +1780,16 @@ c = curr(); } - final byte[] v = tok.trim().toArray(); + final byte[] value = tok.trim().toArray(); if(eq(name.prefix(), DB_PREFIX)) { // project-specific declaration final String key = string(uc(name.local())); final Option opt = qc.context.options.option(key); if(opt == null) throw error(BASX_OPTIONS_X, key); - el.add(new DBPragma(name, opt, v)); + el.add(new DBPragma(name, opt, value)); + } else if(eq(name.prefix(), BASEX_PREFIX)) { + // project-specific declaration + el.add(new BaseXPragma(name, value)); } pos += 2; } while(wsConsumeWs(PRAGMA)); @@ -2060,7 +2068,7 @@ new PartFunc(sc, ii, e, args, holes); } else if(consume(QUESTION)) { // parses the "Lookup" rule - e = new Lookup(info(), keySpecifier(), e); + e = new Lookup(info(), sc, keySpecifier(), e); } } while(e != old); } @@ -2113,14 +2121,14 @@ // unary lookup final int ip = pos; if(consume(QUESTION)) { - if(!wsConsume(COMMA) && !consume(PAREN2)) return new Lookup(info(), keySpecifier()); + if(!wsConsume(COMMA) && !consume(PAREN2)) return new Lookup(info(), sc, keySpecifier()); pos = ip; } // context value if(c == '.' && !digit(next())) { if(next() == '.') return null; consume('.'); - return new Context(info()); + return new ContextValue(info()); } // literals return literal(); @@ -3040,7 +3048,7 @@ wsCheck(PAREN1); skipWs(); - final Test t = elem ? elementTest() : schemaTest(); + final NodeTest t = elem ? elementTest() : schemaTest(); wsCheck(PAREN2); return new DocTest(t != null ? t : Test.ELM); } @@ -3050,7 +3058,7 @@ * @return arguments * @throws QueryException query exception */ - private Test elementTest() throws QueryException { + private NodeTest elementTest() throws QueryException { final QNm name = eQName(null, sc.elemNS); if(name == null && !consume(ASTERISK)) return null; @@ -3064,7 +3072,7 @@ // parse optional question mark wsConsume(QUESTION); } - return new NodeTest(NodeType.ELM, name, type, sc.strip); + return new NodeTest(NodeType.ELM, name, type); } /** @@ -3072,7 +3080,7 @@ * @return arguments * @throws QueryException query exception */ - private Test schemaTest() throws QueryException { + private NodeTest schemaTest() throws QueryException { final QNm name = eQName(QNAME_X, sc.elemNS); throw error(SCHEMAINV_X, name); } @@ -3094,7 +3102,7 @@ if(type == null) type = AtomType.find(tn, true); if(type == null) throw error(TYPEUNDEF_X, tn); } - return new NodeTest(NodeType.ATT, name, type, sc.strip); + return new NodeTest(NodeType.ATT, name, type); } /** @@ -3468,7 +3476,7 @@ } else if(wsConsumeWs(AT)) { final String fn = string(stringLiteral()); // optional: resolve URI reference - final IO fl = qc.stop != null ? qc.stop.get(fn) : sc.io(fn); + final IO fl = qc.stop != null ? qc.stop.get(fn) : sc.resolve(fn, null); if(!opt.sw.read(fl, except)) throw error(NOSTOPFILE_X, fl); } else if(!union && !except) { throw error(FTSTOP); @@ -3504,7 +3512,7 @@ final String fn = string(stringLiteral()); // optional: resolve URI reference - final IO fl = qc.thes != null ? qc.thes.get(fn) : sc.io(fn); + final IO fl = qc.thes != null ? qc.thes.get(fn) : sc.resolve(fn, null); final byte[] rel = wsConsumeWs(RELATIONSHIP) ? stringLiteral() : EMPTY; final Expr[] range = ftRange(true); long min = 0; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/QueryProcessor.java basex-8.2.3/basex-core/src/main/java/org/basex/query/QueryProcessor.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/QueryProcessor.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/QueryProcessor.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,13 +9,14 @@ import org.basex.core.*; import org.basex.core.Context; import org.basex.core.locks.*; -import org.basex.data.*; import org.basex.io.parse.json.*; import org.basex.io.serial.*; import org.basex.query.expr.*; import org.basex.query.iter.*; +import org.basex.query.util.*; import org.basex.query.value.*; import org.basex.query.value.node.*; +import org.basex.query.value.seq.*; /** * This class is an entry point for evaluating XQuery strings. @@ -45,7 +46,7 @@ public QueryProcessor(final String query, final Context ctx) { this.query = query; qc = proc(new QueryContext(ctx)); - sc = new StaticContext(ctx); + sc = new StaticContext(qc); } /** @@ -69,7 +70,8 @@ } /** - * Returns a result iterator. + * Returns a memory-efficient result iterator. In most cases, the query will only be fully + * evaluated if all items of this iterator are requested. * @return result iterator * @throws QueryException query exception */ @@ -79,7 +81,7 @@ } /** - * Returns a result value. + * Evaluates the query and returns the resulting value. * @return result value * @throws QueryException query exception */ @@ -89,13 +91,16 @@ } /** - * Evaluates the specified query and returns the result. + * This function is called by the GUI; use {@link #iter()} or {@link #value()} instead. + * Caches and returns the result of the specified query. If all nodes are of the same database + * instance, the returned value will be of type {@link DBNodes}. + * @param max maximum number of results to cache (negative: return all values) * @return result of query * @throws QueryException query exception */ - public Result execute() throws QueryException { + public Value cache(final int max) throws QueryException { parse(); - return qc.execute(); + return qc.cache(max); } /** @@ -197,6 +202,16 @@ } /** + * Assigns a URI resolver. + * @param resolver resolver + * @return self reference + */ + public QueryProcessor uriResolver(final UriResolver resolver) { + sc.resolver = resolver; + return this; + } + + /** * Returns a serializer for the given output stream. * Optional output declarations within the query will be included in the * serializer instance. @@ -208,7 +223,7 @@ public Serializer getSerializer(final OutputStream os) throws IOException, QueryException { compile(); try { - return Serializer.get(os, qc.serParams()); + return Serializer.get(os, qc.serParams()).sc(sc); } catch(final QueryIOException ex) { throw ex.getCause(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/QueryResources.java basex-8.2.3/basex-core/src/main/java/org/basex/query/QueryResources.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/QueryResources.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/QueryResources.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,8 +11,8 @@ import org.basex.core.users.*; import org.basex.data.*; import org.basex.io.*; -import org.basex.query.iter.*; import org.basex.query.up.*; +import org.basex.query.util.list.*; import org.basex.query.util.pkg.*; import org.basex.query.value.*; import org.basex.query.value.node.*; @@ -50,7 +50,7 @@ private final HashMap, QueryResource> external = new HashMap<>(); /** Pending output. */ - public final ValueBuilder output = new ValueBuilder(); + public final ItemList output = new ItemList(); /** Pending updates. */ Updates updates; @@ -69,9 +69,9 @@ */ Value compile(final DBNodes nodes) { // assign initial context value - final Data data = nodes.data; - final boolean all = nodes.all; - final Value value = DBNodeSeq.get(new IntList(nodes.pres), data, all, all); + final Data data = nodes.data(); + final boolean all = nodes.all(); + final Value value = DBNodeSeq.get(new IntList(nodes.pres()), data, all, all); // create default collection: use initial node set if it contains all // documents of the database. otherwise, create new node set @@ -339,7 +339,7 @@ final QueryInput qi = new QueryInput(paths[n]); nodes[n] = new DBNode(create(qi, true, baseIO, null), 0, Data.DOC); } - addCollection(Seq.get(nodes, ns, NodeType.DOC), name); + addCollection(ValueBuilder.value(nodes, ns, NodeType.DOC), name); } // PRIVATE METHODS ============================================================================== diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/QueryText.java basex-8.2.3/basex-core/src/main/java/org/basex/query/QueryText.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/QueryText.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/QueryText.java 2015-07-14 10:54:40.000000000 +0000 @@ -364,6 +364,8 @@ String READ_LOCK = "read-lock"; /** Option: write-lock. */ String WRITE_LOCK = "write-lock"; + /** Pragma: write-lock. */ + String NON_DETERMNISTIC = "non-deterministic"; // ERROR INFORMATION ======================================================== @@ -437,7 +439,7 @@ /** ID token. */ byte[] ID = token("id"); /** IDRef token. */ - byte[] IDREF = token("idref"); + byte[] IDREF = token("ref"); /** Error token. */ byte[] E_CODE = token("code"); @@ -838,5 +840,4 @@ String DEBUGLOCAL = "Local Variables"; /** Debugging info. */ String DEBUGGLOBAL = "Global Variables"; - } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/StaticContext.java basex-8.2.3/basex-core/src/main/java/org/basex/query/StaticContext.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/StaticContext.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/StaticContext.java 2015-07-14 10:54:40.000000000 +0000 @@ -51,14 +51,15 @@ /** Static Base URI. */ private Uri baseURI = Uri.EMPTY; + /** Sets a module URI resolver. */ + UriResolver resolver; /** * Constructor setting the XQuery version. - * @param ctx database context + * @param qc query context */ - public StaticContext(final Context ctx) { - final MainOptions opts = ctx.options; - mixUpdates = opts.get(MainOptions.MIXUPDATES); + public StaticContext(final QueryContext qc) { + mixUpdates = qc.context.options.get(MainOptions.MIXUPDATES); } /** @@ -88,13 +89,14 @@ } /** - * Returns an IO reference for the specified filename. - * If a base URI exists, it is merged with the specified filename. - * Otherwise, a plain reference is returned. + * Returns an IO reference for the specified path. + * If a base URI exists, it is merged with the path. * @param path file path + * @param uri module namespace (can be {@code null}) * @return io reference */ - IO io(final String path) { + IO resolve(final String path, final String uri) { + if(resolver != null) return resolver.resolve(path, uri, baseURI); final IO base = baseIO(); return base != null ? base.merge(path) : IO.get(path); } @@ -113,15 +115,19 @@ */ public void baseURI(final String uri) { final IO base = IO.get(uri); + String url; if(uri.isEmpty()) { - baseURI = Uri.EMPTY; + url = ""; } else if(base instanceof IOContent) { - baseURI = Uri.uri(uri); + url = uri; } else if(baseURI == Uri.EMPTY) { - baseURI = Uri.uri(base.url()); + url = base.url(); } else { - baseURI = Uri.uri(baseIO().merge(uri).url()); + url = baseIO().merge(uri).url(); } + // #1062: check if specified URI points to a directory. if yes, add trailing slash + if(!url.endsWith("/") && (uri.endsWith(".") || uri.endsWith("/"))) url += '/'; + baseURI = Uri.uri(url); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/AtomicUpdateCache.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/AtomicUpdateCache.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/AtomicUpdateCache.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/AtomicUpdateCache.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,519 @@ +package org.basex.query.up.atomic; + +import java.util.*; + +import org.basex.data.*; +import org.basex.util.*; +import org.basex.util.hash.*; + +/** + * Implementation of the Atomic Update Cache (AUC). + * + *

    A container/list for atomic updates. Updates must be added from the lowest to + * the highest PRE value (regarding the location of the update). Updates are finally + * applied by this container from the highest to the lowest PRE value (reverse document + * order) to support efficient structural bulk updates etc.

    + * + *

    If a collection of updates is carried out via the AUC there are several + * benefits:

    + * + *
      + *
    1. Efficient distance adjustments after structural changes.
    2. + *
    3. Tree-Aware Updates (TAU): identification of superfluous updates (like updating + * the descendants of a deleted node).
    4. + *
    5. Resolution of text node adjacency.
    6. + *
    7. Merging of atomic updates to reduce number of I/Os.
    8. + *
    + * + *

    To avoid ambiguity it is not allowed to add:

    + *
      + *
    • more than one destructive update like {@link Delete} or {@link Replace} operating + * on the same node.
    • + *
    • more than one {@link Rename} or {@link UpdateValue} operating + * on the same node.
    • + *
    • sequences like [delete X, insert N at X]: This sequence would be carried out back + * to front: first the insert, then the delete. This would lead to the inserted node N + * being deleted by the 'delete X' statement. The correct order for this sequence would + * be [insert N at X, delete X].
    • + *
    • and so forth ... see check() function for details.
    • + *
    + * + *

    Updates are added in a streaming fashion where the most recently added update is + * remembered. This avoids additional traversals of the AUC during consistency checks and + * further optimizations.

    + * + * @author BaseX Team 2005-15, BSD License + * @author Lukas Kircher + */ +public final class AtomicUpdateCache { + /** List of structural updates (nodes are inserted to / deleted from the table. */ + private final List struct = new ArrayList<>(1); + /** Value / non-structural updates like rename. */ + private final List val = new ArrayList<>(1); + /** Most recently added update buffer. Used to merge/discard updates and to detect + * inconsistencies on-the-fly eliminating the need to traverse all updates. */ + private BasicUpdate recent; + /** Most recently added structural atomic update - if there is any. Used to calculate accumulated + * pre value shifts on-the-fly, as {@link BasicUpdate} don't carry this information. */ + private BasicUpdate recentStruct; + /** Target data reference. */ + public final Data data; + + /** + * Constructor. + * @param data target data reference + */ + public AtomicUpdateCache(final Data data) { + this.data = data; + } + + /** + * Adds a delete atomic to the list. + * @param pre PRE value of the target node/update location + */ + public void addDelete(final int pre) { + considerAtomic(Delete.getInstance(data, pre), false); + } + + /** + * Adds an insert atomic to the list. + * @param pre PRE value of the target node/update location + * @param par new parent of the inserted nodes + * @param clip insertion sequence data clip + */ + public void addInsert(final int pre, final int par, final DataClip clip) { + considerAtomic(clip.data.kind(clip.start) == Data.ATTR + ? InsertAttr.getInstance(pre, par, clip) : Insert.getInstance(pre, par, clip), false); + } + + /** + * Adds a replace atomic to the list. + * @param pre PRE value of the target node/update location + * @param clip insertion sequence data clip + */ + public void addReplace(final int pre, final DataClip clip) { + considerAtomic(Replace.getInstance(data, pre, clip), false); + } + + /** + * Adds a rename atomic to the list. + * @param pre PRE value of the target node/update location + * @param name new name for the target node + * @param uri new uri for the target node + */ + public void addRename(final int pre, final byte[] name, final byte[] uri) { + considerAtomic(Rename.getInstance(data, pre, name, uri), false); + } + + /** + * Adds an updateValue atomic to the list. + * @param pre PRE value of the target node/update location + * @param value new value for the target node + */ + public void addUpdateValue(final int pre, final byte[] value) { + considerAtomic(UpdateValue.getInstance(data, pre, value), false); + } + + /** + * Resets the list. + */ + public void clear() { + struct.clear(); + val.clear(); + recent = null; + recentStruct = null; + } + + /** + * Adds an update to the corresponding list. + * @param candidate atomic update + * @param slack skip consistency checks etc. if true (used during text node merging) + */ + private void considerAtomic(final BasicUpdate candidate, final boolean slack) { + // fill the one-atomic-update buffer + if(recent == null) { + recent = candidate; + if(recent instanceof StructuralUpdate) + recentStruct = candidate; + return; + } + + if(candidate instanceof StructuralUpdate && recentStruct != null) { + ((StructuralUpdate) candidate).accumulatedShifts += recentStruct.accumulatedShifts(); + } + + // prepare & optimize incoming update + if(slack) { + add(candidate, false); + } else { + check(recent, candidate); + if(treeAwareUpdates(recent, candidate)) return; + + final BasicUpdate m = recent.merge(data, candidate); + if(m != null) add(m, true); + else add(candidate, false); + + } + } + + /** + * Adds the given update to the updates/buffer depending on the type and whether it's + * been merged or not. + * + * @param bu update + * @param merged if true, the given update has been merged w/ the recent one + */ + private void add(final BasicUpdate bu, final boolean merged) { + if(bu == null) return; + + if(!merged) { + if(recent instanceof StructuralUpdate) + struct.add((StructuralUpdate) recent); + else val.add(recent); + } + recent = bu; + if(bu instanceof StructuralUpdate) + recentStruct = bu; + } + + /** + * Flushes the buffer that contains the most previously added atomic update. + */ + private void flush() { + if(recent != null) { + add(recent, false); + recent = null; + recentStruct = null; + } + } + + /** + * Returns the number of structural updates. + * @return number of structural updates + */ + public int updatesSize() { + flush(); + return struct.size() + val.size(); + } + + /** + * Checks the given sequence of two updates for violations. + * + * Updates must be ordered strictly from the lowest to the highest PRE value. + * Deletes must follow inserts. + * + * A single node must not be affected by more than one {@link Rename}, + * {@link UpdateValue} operation. + * + * A single node must not be affected by more than one destructive operation. These + * operations include {@link Replace}, {@link Delete}. + * + * @param bu1 first update in sequence + * @param bu2 second update in sequence + */ + private static void check(final BasicUpdate bu1, final BasicUpdate bu2) { + // check order of location PRE, must be strictly ordered low-to-high + if(bu2.location < bu1.location) + throw Util.notExpected("Invalid order at location " + bu1.location); + + if(bu2.location == bu1.location) { + // check invalid sequence of {@link Delete}, {@link Insert} + // - the inserted node would directly be deleted without this restriction + if(bu2 instanceof Insert || bu2 instanceof InsertAttr) + if(bu1 instanceof Delete) + throw Util.notExpected("Invalid sequence of delete, insert at location " + bu1.location); + else if(bu1 instanceof Replace) + throw Util.notExpected("Invalid sequence of replace, insert at location " + bu1.location); + + // check multiple {@link Delete}, {@link Replace} + if(bu2.destructive() && bu1.destructive()) + throw Util.notExpected("Multiple deletes/replaces on node " + bu1.location); + + // check multiple {@link Rename} + if(bu2 instanceof Rename && bu1 instanceof Rename) + throw Util.notExpected("Multiple renames on node " + bu1.location); + + // check multiple {@link UpdateValue} + if(bu2 instanceof UpdateValue && bu1 instanceof UpdateValue) + throw Util.notExpected("Multiple updates on node " + bu1.location); + + /* Check invalid order of destructive/non-destructive updates to support TAU + * cases like: : node X would be deleted and then X+1 renamed, + * as this shifts down to X. + */ + if(bu2.destructive() && !(bu1 instanceof StructuralUpdate)) + throw Util.notExpected("Invalid sequence of value update and destructive update at" + + " location " + bu1.location); + } + } + + /** + * Checks if the second update is superfluous. An update is considered to be superfluous + * if it targets a position in the subtree of a to-be-removed node. + * @param bu1 first update in sequence + * @param bu2 second update in sequence + * @return true if second update superfluous + */ + private boolean treeAwareUpdates(final BasicUpdate bu1, final BasicUpdate bu2) { + if(bu1.destructive()) { + // we determine the lowest and highest PRE values of a superfluous update + final int pre = bu1.location; + final int fol = pre + data.size(pre, data.kind(pre)); + /* CASE 1: candidate operates on the subtree of T and appends a node to the end of + * the subtree (target PRE may be equal)... + * CASE 2: operates within subtree of T */ + if(bu2.location <= fol && (bu2 instanceof Insert || bu2 instanceof InsertAttr) && + bu2.parent >= pre && bu2.parent < fol || + bu2.location < fol) { + return true; + } + } + return false; + } + + /** + * Executes the updates. Resolving text node adjacency can be skipped if adjacent text + * nodes are not to be expected. + * @param mergeTexts adjacent text nodes are to be expected and must be merged + */ + public void execute(final boolean mergeTexts) { + data.cache = true; + applyUpdates(); + adjustDistances(); + if(mergeTexts) resolveTextAdjacency(); + data.cache = false; + clear(); + } + + /** + * Carries out structural updates. + */ + public void applyUpdates() { + // check if previous update still in buffer + flush(); + // value updates applied front-to-back, doens't matter as there are no row shifts + for(final BasicUpdate u : val) u.apply(data); + // structural updates are applied back-to-front + for(int i = struct.size() - 1; i >= 0; i--) struct.get(i).apply(data); + } + + /** + * Adjusts distances to restore parent-child relationships that have been invalidated + * by structural updates. + * + * Each structural update (insert/delete) leads to a shift of higher PRE values. This + * invalidates parent-child relationships. Distances are only adjusted after all + * structural updates have been carried out to make sure each node (that has to be + * updated) is only touched once. + */ + private void adjustDistances() { + // check if any distance has changed at all + boolean shifts = false; + for(final StructuralUpdate update : struct) { + if(update.accumulatedShifts != 0) { + shifts = true; + break; + } + } + if(!shifts) return; + + final IntSet updatedNodes = new IntSet(); + for(final StructuralUpdate update : struct) { + /* Update distance for the affected node and all following siblings of nodes + * on the ancestor-or-self axis. */ + int pre = update.preOfAffectedNode + update.accumulatedShifts; + while(pre < data.meta.size && !updatedNodes.contains(pre)) { + final int kind = data.kind(pre); + data.dist(pre, kind, calculateNewDistance(pre, kind)); + updatedNodes.add(pre); + pre += data.size(pre, kind); + } + } + } + + /** + * Calculates the new distance value for the given node after updates have been applied. + * @param pre the new PRE value of the node after structural updates have been applied + * @param kind the KIND value + * @return new distance for the given PRE node + */ + private int calculateNewDistance(final int pre, final int kind) { + int distanceBefore = data.dist(pre, kind); + final int preBefore = calculatePreValue(pre, true); + // document distances are not stored in table but calculated on the fly (always pre+1) + if(kind == Data.DOC) distanceBefore = preBefore + 1; + final int parentBefore = preBefore - distanceBefore; + final int parentAfter = calculatePreValue(parentBefore, false); + return pre - parentAfter; + } + + /** + * Calculates the PRE value of a given node before/after updates. + * + * Finds all updates that affect the given node N. The result is than calculated based + * on N and the accumulated PRE value shifts introduced by these updates. + * + * If a node has been inserted at position X and this method is used to calculate the + * PRE value of X before updates, X is the result. As the node at position X has not + * existed before the insertion, its PRE value is unchanged. If in contrast the PRE + * value is calculated after updates, the result is X+1, as the node with the original + * position X has been shifted by the insertion at position X. + * + * Make sure accumulated shifts have been calculated before calling this method! + * + * @param pre PRE value + * @param beforeUpdates calculate PRE value before shifts/updates have been applied + * @return index of update, or -1 + */ + public int calculatePreValue(final int pre, final boolean beforeUpdates) { + // find update that affects the given PRE value + int i = find(pre, beforeUpdates); + // given PRE not changed by updates + if(i == -1) return pre; + // refine the search to determine accumulated shifts for the given PRE + i = refine(struct, i, beforeUpdates); + final int acm = struct.get(i).accumulatedShifts; + return beforeUpdates ? pre - acm : pre + acm; + } + + /** + * Used to find the update that holds the accumulated shift value that is needed to + * recalculate the given PRE value. In a low-to-high ordered list this is the right-most + * update with a target PRE value smaller or equal the given PRE value, v.v. + * + * Finds the position of the update that affects the given PRE value P. + * If there are multiple updates whose affected PRE value equals P, the search + * has to be further refined as this method returns only the first match. + * @param pre given PRE value + * @param beforeUpdates compare based on PRE values before/after updates + * @return index of update + */ + private int find(final int pre, final boolean beforeUpdates) { + int left = 0; + int right = struct.size() - 1; + + while(left <= right) { + if(left == right) { + if(c(struct, left, beforeUpdates) <= pre) return left; + return -1; + } + if(right - left == 1) { + if(c(struct, right, beforeUpdates) <= pre) return right; + if(c(struct, left, beforeUpdates) <= pre) return left; + return -1; + } + final int middle = left + right >>> 1; + final int value = c(struct, middle, beforeUpdates); + if(value == pre) return middle; + else if(value > pre) right = middle - 1; + else left = middle; + } + + // empty array + return -1; + } + + /** + * Finds the update with the lowest index in the given list that affects the same + * PRE value as the update with the given index. + * @param updates list of updates + * @param index of update + * @param beforeUpdates find update for PRE values before updates have been applied + * @return update with the highest index that invalidates the distance of the given + * node + */ + private static int refine(final List updates, final int index, + final boolean beforeUpdates) { + int u = index; + final int value = c(updates, u++, beforeUpdates); + final int us = updates.size(); + while(u < us && c(updates, u, beforeUpdates) == value) u++; + return u - 1; + } + + /** + * Recalculates the PRE value of the first node whose distance is affected by the + * given update. + * @param updates list of updates + * @param index index of the update + * @param beforeUpdates calculate PRE value before or after updates + * @return PRE value + */ + private static int c(final List updates, final int index, + final boolean beforeUpdates) { + final StructuralUpdate u = updates.get(index); + return u.preOfAffectedNode + (beforeUpdates ? u.accumulatedShifts : 0); + } + + /** + * Resolves unwanted text node adjacency which can result from structural changes in + * the database. Adjacent text nodes are two text nodes A and B, where + * PRE(B)=PRE(A)+1 and PARENT(A)=PARENT(B). + */ + private void resolveTextAdjacency() { + // Text node merges are also gathered on a separate list to leverage optimizations. + final List deletes = new LinkedList<>(); + + // keep track of the visited locations to avoid superfluous checks + int smallestVisited = Integer.MAX_VALUE; + // Text nodes have to be merged from the highest to the lowest pre value + for(int i = struct.size() - 1; i >= 0; i--) { + final StructuralUpdate u = struct.get(i); + final DataClip insseq = u.getInsertionData(); + // calculate the new location of the update, here we have to check for adjacency + final int newLocation = u.location + u.accumulatedShifts - u.shifts; + final int beforeNewLocation = newLocation - 1; + // check surroundings of this location for adjacent text nodes depending on the + // kind of update, first the one with higher PRE values (due to shifts!) + // ... for insert/replace ... + if(insseq != null) { + // calculate the current following node + final int followingNode = newLocation + insseq.size(); + final int beforeFollowingNode = followingNode - 1; + // check the nodes at the end of/after the insertion sequence + if(beforeFollowingNode < smallestVisited) { + final Delete del = mergeTextNodes(beforeFollowingNode); + if(del != null) deletes.add(0, del); + smallestVisited = beforeFollowingNode; + } + } + // check nodes for delete and for insert before the updated location + if(beforeNewLocation < smallestVisited) { + final Delete del = mergeTextNodes(beforeNewLocation); + if(del != null) deletes.add(0, del); + smallestVisited = beforeNewLocation; + } + } + + final AtomicUpdateCache auc = new AtomicUpdateCache(data); + for(final Delete delete : deletes) auc.considerAtomic(delete, true); + deletes.clear(); + auc.applyUpdates(); + auc.adjustDistances(); + auc.clear(); + } + + /** + * Returns atomic text node merging operations if necessary for the given node PRE and + * its right neighbor PRE+1. + * @param pre node PRE value + * @return list of text merging operations + */ + private Delete mergeTextNodes(final int pre) { + final int s = data.meta.size; + final int b = pre + 1; + // don't leave table + if(pre >= s || b >= s || pre < 0 || b < 0) return null; + // only merge texts + if(data.kind(pre) != Data.TEXT || data.kind(b) != Data.TEXT) return null; + // only merge neighboring texts + if(data.parent(pre, Data.TEXT) != data.parent(b, Data.TEXT)) return null; + + // apply text node updates on the fly and throw them away + UpdateValue.getInstance(data, pre, Token.concat(data.text(pre, true), + data.text(b, true))). + apply(data); + // deletes must be cached to add them front-to-back to atomic update list + return Delete.getInstance(data, b); + } +} \ No newline at end of file diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/BasicUpdate.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/BasicUpdate.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/BasicUpdate.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/BasicUpdate.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,70 @@ +package org.basex.query.up.atomic; + +import org.basex.data.*; + +/** + * Abstract atomic update. + * Atomic updates can only be initialized via {@link AtomicUpdateCache}. + * + * @author BaseX Team 2005-15, BSD License + * @author Lukas Kircher + */ +public abstract class BasicUpdate { + /** PRE value of the target location. */ + final int location; + /** Parent PRE of nodes to insert. */ + final int parent; + + /** + * Constructor. + * @param location target node location PRE + * @param parent parent node PRE value + */ + BasicUpdate(final int location, final int parent) { + this.location = location; + this.parent = parent; + } + + /** + * Getter for accumulated shifts. + * @return accumulated shifts, or zero if non-structural update. + */ + int accumulatedShifts() { + return 0; + } + + /** + * Applies the update to the given data instance. + * @param data data instance on which to execute the update + */ + abstract void apply(final Data data); + + /** + * Returns the data to be inserted (for inserts,...). + * @return Insertion sequence data instance + */ + abstract DataClip getInsertionData(); + + /** + * Returns whether this updates destroys the target nodes identity. Used to determine + * superfluous operations on the subtree of the target. + * @return {@code true} if target node identity destroyed + */ + abstract boolean destructive(); + + /** + * Merges the given update and this update if possible. + * @param data data reference + * @param bu update to merge with + * @return merged atomic update or null if merge not possible + */ + @SuppressWarnings("unused") + public BasicUpdate merge(final Data data, final BasicUpdate bu) { + return null; + } + + @Override + public String toString() { + return "L" + location; + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/Delete.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/Delete.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/Delete.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/Delete.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,67 @@ +package org.basex.query.up.atomic; + +import org.basex.data.*; + +/** + * Atomic update operation that deletes a node. + * + * @author BaseX Team 2005-15, BSD License + * @author Lukas Kircher + */ +final class Delete extends StructuralUpdate { + /** + * Constructor. + * @param location target node location PRE + * @param shifts PRE value shifts introduced by update + * @param acc accumulated shifts + * @param first PRE value of the first node which distance has to be updated + * @param parent parent + */ + private Delete(final int location, final int shifts, final int acc, final int first, + final int parent) { + super(location, shifts, acc, first, parent); + } + + /** + * Factory. + * @param data data reference + * @param pre location pre value + * @return atomic delete + */ + static Delete getInstance(final Data data, final int pre) { + final int k = data.kind(pre); + final int s = data.size(pre, k); + return new Delete(pre, -s, -s, pre + s, data.parent(pre, k)); + } + + @Override + void apply(final Data data) { + data.delete(location); + } + + @Override + DataClip getInsertionData() { + return null; + } + + @Override + boolean destructive() { + return true; + } + + @Override + public String toString() { + return "\n Delete: " + super.toString(); + } + + @Override + public BasicUpdate merge(final Data data, final BasicUpdate bu) { + if(bu != null && parent == bu.parent && bu instanceof Insert && + location - shifts == bu.location && data.kind(location) != Data.ATTR) { + final Insert ins = (Insert) bu; + return new Replace(location, shifts + ins.shifts, + ins.accumulatedShifts, preOfAffectedNode, ins.clip, parent); + } + return null; + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/InsertAttr.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/InsertAttr.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/InsertAttr.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/InsertAttr.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,61 @@ +package org.basex.query.up.atomic; + +import org.basex.data.*; + +/** + * Atomic update operation that inserts an attribute into a database. + * + * @author BaseX Team 2005-15, BSD License + * @author Lukas Kircher + */ +final class InsertAttr extends StructuralUpdate { + /** Insertion sequence. */ + private final DataClip clip; + + /** + * Constructor. + * @param location PRE value of the target node location + * @param shifts row shifts + * @param acc accumulated row shifts + * @param first PRE value of the first node which distance has to be updated + * @param parent parent PRE value for the inserted node + * @param clip insert sequence data clip + */ + private InsertAttr(final int location, final int shifts, final int acc, final int first, + final int parent, final DataClip clip) { + super(location, shifts, acc, first, parent); + this.clip = clip; + } + + /** + * Factory. + * @param pre target location PRE + * @param par parent of new attribute + * @param clip insertion sequence + * @return instance + */ + static InsertAttr getInstance(final int pre, final int par, final DataClip clip) { + final int sh = clip.size(); + return new InsertAttr(pre, sh, sh, pre, par, clip); + } + + @Override + void apply(final Data data) { + data.insertAttr(location, parent, clip); + } + + @Override + DataClip getInsertionData() { + return clip; + } + + @Override + boolean destructive() { + return false; + } + + @Override + public String toString() { + return "\nInsertAttr: " + super.toString(); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/Insert.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/Insert.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/Insert.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/Insert.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,72 @@ +package org.basex.query.up.atomic; + +import org.basex.data.*; + +/** + * Atomic insert that inserts a given insertion sequence data instance into a database. + * + * @author BaseX Team 2005-15, BSD License + * @author Lukas Kircher + */ +final class Insert extends StructuralUpdate { + /** Insertion sequence. */ + final DataClip clip; + + /** + * Constructor. + * @param location PRE value of the target node location + * @param shifts shifts + * @param acc accumulated shifts + * @param first PRE value of the first node which distance has to be updated + * @param parent parent PRE value for the inserted nodes + * @param clip insertion sequence data clip + */ + private Insert(final int location, final int shifts, final int acc, final int first, + final int parent, final DataClip clip) { + super(location, shifts, acc, first, parent); + this.clip = clip; + } + + /** + * Factory. + * @param pre target location PRE + * @param par parent of inserted node + * @param clip insertion sequence + * @return instance + */ + static Insert getInstance(final int pre, final int par, final DataClip clip) { + final int s = clip.size(); + return new Insert(pre, s, s, pre, par, clip); + } + + @Override + void apply(final Data data) { + data.insert(location, parent, clip); + } + + @Override + DataClip getInsertionData() { + return clip; + } + + @Override + boolean destructive() { + return false; + } + + @Override + public BasicUpdate merge(final Data data, final BasicUpdate bu) { + if(bu != null && parent == bu.parent && bu instanceof Delete && location == bu.location + && data.kind(bu.location) != Data.ATTR) { + final Delete del = (Delete) bu; + return new Replace(location, shifts + del.shifts, + del.accumulatedShifts, del.preOfAffectedNode, clip, parent); + } + return null; + } + + @Override + public String toString() { + return "\n Insert: " + super.toString(); + } +} \ No newline at end of file diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/Rename.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/Rename.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/Rename.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/Rename.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,68 @@ +package org.basex.query.up.atomic; + +import org.basex.data.*; +import org.basex.util.*; + +/** + * Atomic update operation that renames a node. + * + * @author BaseX Team 2005-15, BSD License + * @author Lukas Kircher + */ +final class Rename extends BasicUpdate { + /** Kind of updated node. */ + private final int kind; + /** The new name of the node. */ + private final byte[] name; + /** Name URI. */ + private final byte[] uri; + + /** + * Constructor. + * @param location PRE value of target node location + * @param kind target node kind + * @param name new name for the target node + * @param uri new name uri for the target node + * @param parent parent node PRE + */ + private Rename(final int location, final int kind, final byte[] name, final byte[] uri, + final int parent) { + super(location, parent); + if(name.length == 0) throw Util.notExpected("New name must not be empty."); + this.kind = kind; + this.name = name; + this.uri = uri; + } + + /** + * Factory. + * @param data data reference + * @param pre target node PRE + * @param name new name + * @param uri new uri + * @return instance + */ + static Rename getInstance(final Data data, final int pre, final byte[] name, final byte[] uri) { + return new Rename(pre, data.kind(pre), name, uri, data.parent(pre, data.kind(pre))); + } + + @Override + void apply(final Data data) { + data.update(location, kind, name, uri); + } + + @Override + DataClip getInsertionData() { + throw Util.notExpected("No insertion sequence needed for atomic rename operation."); + } + + @Override + boolean destructive() { + return false; + } + + @Override + public String toString() { + return "\n Rename: " + super.toString(); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/Replace.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/Replace.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/Replace.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/Replace.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,150 @@ +package org.basex.query.up.atomic; + +import static org.basex.util.Token.*; + +import java.util.*; + +import org.basex.data.*; + +/** + * Replaces a node in the database with an insertion sequence. + * + * @author BaseX Team 2005-15, BSD License + * @author Lukas Kircher + */ +final class Replace extends StructuralUpdate { + /** Insertion sequence. */ + private final DataClip clip; + + /** + * Constructor. + * @param location PRE value of the target node location + * @param clip insertion sequence data clip + * @param shifts PRE value shifts introduced by update + * @param acc accumulated shifts + * @param first PRE value of the first node which distance has to be updated + * @param parent parent node PRE + */ + Replace(final int location, final int shifts, final int acc, final int first, final DataClip clip, + final int parent) { + super(location, shifts, acc, first, parent); + this.clip = clip; + } + + /** + * Factory. + * @param data data reference + * @param pre target node PRE + * @param clip insertion sequence + * @return instance + */ + static Replace getInstance(final Data data, final int pre, final DataClip clip) { + final int kind = data.kind(pre); + final int par = data.parent(pre, kind); + final int oldsize = data.size(pre, kind); + final int newsize = clip.size(); + final int sh = newsize - oldsize; + return new Replace(pre, sh, sh, pre + oldsize, clip, par); + } + + @Override + void apply(final Data data) { + // [LK] replace optimizations only work without namespaces.. + if(data.nspaces.isEmpty() && clip.data.nspaces.isEmpty()) { + // Lazy Replace: rewrite to value updates if structure has not changed + if(lazyReplace(data)) return; + // Rapid Replace: in-place update, overwrite existing table entries + data.replace(location, clip); + } else { + // fallback: delete old entries, add new ones + final int kind = data.kind(location); + final int par = data.parent(location, kind); + // delete first - otherwise insert must be at location+1 + data.delete(location); + if(kind == Data.ATTR) { + data.insertAttr(location, par, clip); + } else { + data.insert(location, par, clip); + } + } + } + + /** + * Lazy Replace implementation. Checks if the replace operation can be substituted with + * cheaper value updates. If structural changes have to be made no substitution takes place. + * @param data destination data reference + * @return true if substitution successful + */ + private boolean lazyReplace(final Data data) { + final Data src = clip.data; + final int srcSize = clip.size(); + // check for equal subtree size + if(srcSize != data.size(location, data.kind(location))) return false; + + final List valueUpdates = new ArrayList<>(); + for(int c = 0; c < srcSize; c++) { + final int s = clip.start + c; + final int t = location + c; + final int sk = src.kind(s); + final int tk = data.kind(t); + + if(sk != tk) + return false; + // distance can differ for first two tuples + if(c > 0 && src.dist(s, sk) != data.dist(t, tk)) + return false; + // check texts, comments and documents + if(sk == Data.TEXT || sk == Data.COMM || sk == Data.DOC) { + final byte[] srcText = src.text(s, true); + if(!eq(data.text(t, true), srcText)) + valueUpdates.add(UpdateValue.getInstance(data, t, srcText)); + } else { + // check elements, attributes and processing instructions + final byte[] srcName = src.name(s, sk); + final byte[] trgName = data.name(t, tk); + if(!eq(srcName, trgName)) valueUpdates.add(Rename.getInstance(data, t, srcName, EMPTY)); + switch(sk) { + case Data.ELEM: + // check size of elements + if(src.attSize(s, sk) != data.attSize(t, tk) || src.size(s, sk) != data.size(t, tk)) + return false; + break; + case Data.ATTR: + // check attribute values + final byte[] av = src.text(s, false); + if(!eq(data.text(t, false), av)) + valueUpdates.add(UpdateValue.getInstance(data, t, av)); + break; + case Data.PI: + // check processing instruction value + final byte[] srcText = src.text(s, true); + final byte[] trgText = data.text(t, true); + final int i = indexOf(srcText, ' '); + final byte[] pv = i == -1 ? EMPTY : substring(srcText, i + 1); + if(!eq(pv, indexOf(trgText, ' ') == -1 ? EMPTY : + substring(trgText, i + 1))) { + valueUpdates.add(UpdateValue.getInstance(data, t, pv)); + } + break; + } + } + } + for(final BasicUpdate bu : valueUpdates) bu.apply(data); + return true; + } + + @Override + DataClip getInsertionData() { + return clip; + } + + @Override + boolean destructive() { + return true; + } + + @Override + public String toString() { + return "\nReplace: " + super.toString(); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/StructuralUpdate.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/StructuralUpdate.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/StructuralUpdate.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/StructuralUpdate.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,48 @@ +package org.basex.query.up.atomic; + +/** + * Base class for structural updates that add to/remove from the table and introduce shifts. + * @author BaseX Team 2005-15, BSD License + * @author Lukas Kircher + */ +abstract class StructuralUpdate extends BasicUpdate { + /** PRE value shifts introduced by this atomic update due to structural changes. */ + final int shifts; + /** PRE value of the first node for which the distance must be updated due to PRE value + * shifts introduced by this update. */ + final int preOfAffectedNode; + /** Total/accumulated number of shifts introduced by all updates on the list up to this + * update (inclusive). The number of total shifts is used to calculate PRE values + * before/after updates. */ + int accumulatedShifts; + + /** + * Constructor. + * @param location target node location PRE + * @param shifts PRE value shifts introduced by update + * @param acc accumulated shifts + * @param first PRE value of the first node the distance of which has to be updated + * @param parent parent node + */ + StructuralUpdate(final int location, final int shifts, final int acc, final int first, + final int parent) { + super(location, parent); + this.shifts = shifts; + accumulatedShifts = acc; + preOfAffectedNode = first; + } + + @Override + int accumulatedShifts() { + return accumulatedShifts; + } + + @Override + public String toString() { + return "L" + location + + " PAR" + parent + + " SHF" + shifts + + " ASHF" + accumulatedShifts + + " AFF" + preOfAffectedNode; + } +} \ No newline at end of file diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/UpdateValue.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/UpdateValue.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/atomic/UpdateValue.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/atomic/UpdateValue.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,62 @@ +package org.basex.query.up.atomic; + +import org.basex.data.*; +import org.basex.util.*; + +/** + * Atomic update operation that replaces the value of a single text, comment, pi or + * attribute node. + * + * @author BaseX Team 2005-15, BSD License + * @author Lukas Kircher + */ +final class UpdateValue extends BasicUpdate { + /** Target node kind. */ + private final int kind; + /** New value for target node. */ + private final byte[] value; + + /** + * Constructor. + * @param location PRE value of the target node location + * @param kind node kind of the target node + * @param value new value which is assigned to the target node + * @param parent parent of updated node + */ + private UpdateValue(final int location, final int kind, final byte[] value, final int parent) { + super(location, parent); + this.kind = kind; + this.value = value; + } + + /** + * Factory. + * @param data data reference + * @param pre PRE value of the target node location + * @param value new value which is assigned to the target node + * @return new instance + */ + static UpdateValue getInstance(final Data data, final int pre, final byte[] value) { + return new UpdateValue(pre, data.kind(pre), value, data.parent(pre, data.kind(pre))); + } + + @Override + void apply(final Data data) { + data.update(location, kind, value); + } + + @Override + DataClip getInsertionData() { + throw Util.notExpected("No insertion sequence needed for atomic value update operation."); + } + + @Override + boolean destructive() { + return false; + } + + @Override + public String toString() { + return "\nUpdateValue: " + super.toString(); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/DataUpdates.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/DataUpdates.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/DataUpdates.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/DataUpdates.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,9 +9,9 @@ import org.basex.core.*; import org.basex.core.cmd.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.*; import org.basex.query.func.fn.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.query.up.primitives.db.*; import org.basex.query.up.primitives.node.*; @@ -279,8 +279,8 @@ if(data.kind(pre) == Data.ATTR) { final byte[] nm = data.name(pre, Data.ATTR); final QNm name = new QNm(nm); - final byte[] uri = data.nspaces.uri(data.nspaces.uri(nm, pre, data)); - if(uri != null) name.uri(uri); + final int uriId = data.nspaces.uriIdForPrefix(Token.prefix(nm), pre, data); + if(uriId != 0) name.uri(data.nspaces.uri(uriId)); pool.add(name, NodeType.ATT); il.add(pre); } else { @@ -289,8 +289,8 @@ final byte[] nm = data.name(p, Data.ATTR); if(!il.contains(p)) { final QNm name = new QNm(nm); - final byte[] uri = data.nspaces.uri(data.nspaces.uri(nm, p, data)); - if(uri != null) name.uri(uri); + final int uriId = data.nspaces.uriIdForPrefix(Token.prefix(nm), p, data); + if(uriId != 0) name.uri(data.nspaces.uri(uriId)); pool.add(name, NodeType.ATT); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/expr/Delete.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/expr/Delete.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/expr/Delete.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/expr/Delete.java 2015-07-14 10:54:40.000000000 +0000 @@ -41,7 +41,7 @@ if(n.parent() == null) continue; final Updates updates = qc.resources.updates(); final DBNode dbn = updates.determineDataRef(n, qc); - updates.add(new DeleteNode(dbn.pre, dbn.data, info), qc); + updates.add(new DeleteNode(dbn.pre(), dbn.data(), info), qc); } return null; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/expr/Insert.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/expr/Insert.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/expr/Insert.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/expr/Insert.java 2015-07-14 10:54:40.000000000 +0000 @@ -88,18 +88,18 @@ if(targ.type != NodeType.ELM) throw (before || after ? UPATTELM : UPATTELM2).get(info); dbn = updates.determineDataRef(targ, qc); - up = new InsertAttribute(dbn.pre, dbn.data, info, checkNS(aList, targ)); + up = new InsertAttribute(dbn.pre(), dbn.data(), info, checkNS(aList, targ)); updates.add(up, qc); } // no update primitive is created if node list is empty if(!cList.isEmpty()) { dbn = updates.determineDataRef(n, qc); - if(before) up = new InsertBefore(dbn.pre, dbn.data, info, cList); - else if(after) up = new InsertAfter(dbn.pre, dbn.data, info, cList); - else if(first) up = new InsertIntoAsFirst(dbn.pre, dbn.data, info, cList); - else if(last) up = new InsertIntoAsLast(dbn.pre, dbn.data, info, cList); - else up = new InsertInto(dbn.pre, dbn.data, info, cList); + if(before) up = new InsertBefore(dbn.pre(), dbn.data(), info, cList); + else if(after) up = new InsertAfter(dbn.pre(), dbn.data(), info, cList); + else if(first) up = new InsertIntoAsFirst(dbn.pre(), dbn.data(), info, cList); + else if(last) up = new InsertIntoAsLast(dbn.pre(), dbn.data(), info, cList); + else up = new InsertInto(dbn.pre(), dbn.data(), info, cList); updates.add(up, qc); } return null; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/expr/Modify.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/expr/Modify.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/expr/Modify.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/expr/Modify.java 2015-07-14 10:54:40.000000000 +0000 @@ -57,7 +57,7 @@ @Override public Value value(final QueryContext qc) throws QueryException { - final int o = (int) qc.resources.output.size(); + final int o = qc.resources.output.size(); final Updates updates = qc.resources.updates(); final ContextModifier tmp = updates.mod; final TransformModifier pu = new TransformModifier(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/expr/Rename.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/expr/Rename.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/expr/Rename.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/expr/Rename.java 2015-07-14 10:54:40.000000000 +0000 @@ -72,7 +72,7 @@ final Updates updates = qc.resources.updates(); final DBNode dbn = updates.determineDataRef(targ, qc); - updates.add(new RenameNode(dbn.pre, dbn.data, info, rename), qc); + updates.add(new RenameNode(dbn.pre(), dbn.data(), info, rename), qc); return null; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/expr/Replace.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/expr/Replace.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/expr/Replace.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/expr/Replace.java 2015-07-14 10:54:40.000000000 +0000 @@ -70,7 +70,7 @@ if(tp == NodeType.COM) FComm.parse(txt, info); if(tp == NodeType.PI) FPI.parse(txt, info); - updates.add(new ReplaceValue(dbn.pre, dbn.data, info, txt), qc); + updates.add(new ReplaceValue(dbn.pre(), dbn.data(), info, txt), qc); } else { final ANode par = targ.parent(); if(par == null) throw UPNOPAR_X.get(info, i); @@ -83,7 +83,7 @@ if(!aList.isEmpty()) throw UPWRELM.get(info); } // conforms to specification: insertion sequence may be empty - updates.add(new ReplaceNode(dbn.pre, dbn.data, info, list), qc); + updates.add(new ReplaceNode(dbn.pre(), dbn.data(), info, list), qc); } return null; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/expr/Transform.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/expr/Transform.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/expr/Transform.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/expr/Transform.java 2015-07-14 10:54:40.000000000 +0000 @@ -61,7 +61,7 @@ @Override public Value value(final QueryContext qc) throws QueryException { - final int o = (int) qc.resources.output.size(); + final int o = qc.resources.output.size(); final Updates updates = qc.resources.updates(); final ContextModifier tmp = updates.mod; final TransformModifier pu = new TransformModifier(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/db/DBAdd.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/db/DBAdd.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/db/DBAdd.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/db/DBAdd.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,7 +3,6 @@ import java.util.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.*; import org.basex.query.up.primitives.*; import org.basex.util.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/db/Put.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/db/Put.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/db/Put.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/db/Put.java 2015-07-14 10:54:40.000000000 +0000 @@ -49,7 +49,7 @@ final DBNode node = new DBNode(data, pre); try(final PrintOutput po = new PrintOutput(u)) { // try to reproduce non-chopped documents correctly - final SerializerOptions so = SerializerOptions.get(node.data.meta.chop); + final SerializerOptions so = SerializerOptions.get(node.data().meta.chop); try(final Serializer ser = Serializer.get(po, so)) { ser.serialize(node); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/DBNew.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/DBNew.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/DBNew.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/DBNew.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,7 +9,6 @@ import org.basex.build.*; import org.basex.core.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.io.*; import org.basex.query.*; import org.basex.util.*; @@ -92,7 +91,7 @@ // add document node final Context ctx = qc.context; if(ni.node != null) { - final MemData mdata = (MemData) ni.node.dbCopy(options).data; + final MemData mdata = (MemData) ni.node.dbCopy(options).data(); mdata.update(0, Data.DOC, ni.path); return new DataClip(mdata); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/name/DBCreate.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/name/DBCreate.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/name/DBCreate.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/name/DBCreate.java 2015-07-14 10:54:40.000000000 +0000 @@ -10,7 +10,6 @@ import org.basex.core.*; import org.basex.core.cmd.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.*; import org.basex.query.func.*; import org.basex.query.up.primitives.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/DeleteNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/DeleteNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/DeleteNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/DeleteNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,8 +1,8 @@ package org.basex.query.up.primitives.node; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.up.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.query.value.node.*; import org.basex.util.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertAfter.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertAfter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertAfter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertAfter.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,8 +1,8 @@ package org.basex.query.up.primitives.node; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.up.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.query.util.list.*; import org.basex.query.value.node.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertAttribute.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertAttribute.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertAttribute.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertAttribute.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,8 +1,8 @@ package org.basex.query.up.primitives.node; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.up.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.query.util.list.*; import org.basex.query.value.node.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertBefore.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertBefore.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertBefore.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertBefore.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,8 +1,8 @@ package org.basex.query.up.primitives.node; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.up.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.query.util.list.*; import org.basex.query.value.node.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertIntoAsFirst.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertIntoAsFirst.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertIntoAsFirst.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertIntoAsFirst.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,8 +1,8 @@ package org.basex.query.up.primitives.node; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.up.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.query.util.list.*; import org.basex.query.value.node.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertIntoAsLast.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertIntoAsLast.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertIntoAsLast.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertIntoAsLast.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,8 +1,8 @@ package org.basex.query.up.primitives.node; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.up.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.query.util.list.*; import org.basex.query.value.node.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertInto.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertInto.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertInto.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/InsertInto.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,8 +1,8 @@ package org.basex.query.up.primitives.node; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.up.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.query.util.list.*; import org.basex.query.value.node.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/NodeCopy.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/NodeCopy.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/NodeCopy.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/NodeCopy.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,6 @@ package org.basex.query.up.primitives.node; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.up.*; import org.basex.query.up.primitives.*; import org.basex.query.util.*; @@ -46,8 +45,7 @@ // build main memory representation of nodes to be copied final int start = tmp.meta.size; new DataBuilder(tmp).build(list); - insseq = new DataClip(tmp, start, tmp.meta.size); - insseq.fragments = list.size(); + insseq = new DataClip(tmp, start, tmp.meta.size, list.size()); } /** @@ -57,16 +55,15 @@ */ final void add(final NamePool pool) { final Data d = insseq.data; - final int ps = insseq.start; - final int pe = insseq.end; - for(int p = ps; p < pe; ++p) { - final int k = d.kind(p); - if(k != Data.ATTR && k != Data.ELEM) continue; - if(p > ps && d.parent(p, k) >= ps) break; - final int u = d.uri(p, k); - final QNm qnm = new QNm(d.name(p, k)); - if(u != 0) qnm.uri(d.nspaces.uri(u)); - pool.add(qnm, ANode.type(k)); + final int s = insseq.start, e = insseq.end; + for(int p = s; p < e; ++p) { + final int kind = d.kind(p); + if(kind != Data.ATTR && kind != Data.ELEM) continue; + if(p > s && d.parent(p, kind) >= s) break; + final int uriId = d.uriId(p, kind); + final QNm qnm = new QNm(d.name(p, kind)); + if(uriId != 0) qnm.uri(d.nspaces.uri(uriId)); + pool.add(qnm, ANode.type(kind)); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/NodeUpdate.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/NodeUpdate.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/NodeUpdate.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/NodeUpdate.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,9 +1,9 @@ package org.basex.query.up.primitives.node; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.*; import org.basex.query.up.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.query.value.node.*; import org.basex.util.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/RenameNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/RenameNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/RenameNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/RenameNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,9 +3,9 @@ import static org.basex.query.QueryError.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.*; import org.basex.query.up.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/ReplaceDoc.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/ReplaceDoc.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/ReplaceDoc.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/ReplaceDoc.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,9 +5,9 @@ import java.util.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.*; import org.basex.query.up.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.util.*; import org.basex.util.options.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/ReplaceNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/ReplaceNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/ReplaceNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/ReplaceNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,9 +3,9 @@ import static org.basex.query.QueryError.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.*; import org.basex.query.up.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.query.util.list.*; import org.basex.util.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/ReplaceValue.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/ReplaceValue.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/node/ReplaceValue.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/node/ReplaceValue.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,9 +5,9 @@ import java.util.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.*; import org.basex.query.up.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.query.util.list.*; import org.basex.query.value.node.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/UserUpdate.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/UserUpdate.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/primitives/UserUpdate.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/primitives/UserUpdate.java 2015-07-14 10:54:40.000000000 +0000 @@ -65,7 +65,7 @@ public final void merge(final Update update) throws QueryException { final String db = ((UserUpdate) update).patterns.get(0); if(patterns.contains(db)) { - if(db == null) throw USER_UPDATE_X_X.get(info, name(), operation()); + if(db.isEmpty()) throw USER_UPDATE_X_X.get(info, name(), operation()); throw USER_UPDATE_X_X_X.get(info, db, name(), operation()); } patterns.add(db); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/up/Updates.java basex-8.2.3/basex-core/src/main/java/org/basex/query/up/Updates.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/up/Updates.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/up/Updates.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,9 +3,9 @@ import java.util.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.*; import org.basex.query.up.primitives.node.*; import org.basex.query.value.node.*; @@ -93,7 +93,7 @@ // determine highest ancestor node ANode anc = target; - final AxisIter it = target.ancestor(); + final BasicNodeIter it = target.ancestor(); for(ANode p; (p = it.next()) != null;) anc = p; /* See if this ancestor has already been added to the pending update list. @@ -103,7 +103,7 @@ MemData data = fragmentIDs.get(ancID); // if data doesn't exist, create a new one if(data == null) { - data = (MemData) anc.dbCopy(qc.context.options).data; + data = (MemData) anc.dbCopy(qc.context.options).data(); // create a mapping between the fragment id and the data reference fragmentIDs.put(ancID, data); } @@ -163,7 +163,7 @@ if(node.id == trgID) return 0; int s = 1; - AxisIter it = node.attributes(); + BasicNodeIter it = node.attributes(); for(ANode n; (n = it.next()) != null;) { final int st = preSteps(n, trgID); if(st == 0) return s; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/Ann.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/Ann.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/Ann.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/Ann.java 2015-07-14 10:54:40.000000000 +0000 @@ -15,8 +15,10 @@ public final class Ann { /** Input info. */ public final InputInfo info; - /** Annotation signature. */ + /** Annotation signature (is {@code null} if {@link #name} is assigned). */ public final Annotation sig; + /** No-standard annotation (is {@code null} if {@link #sig} is assigned). */ + public final QNm name; /** Arguments. */ public final Item[] args; @@ -28,27 +30,43 @@ */ public Ann(final InputInfo info, final Annotation sig, final Item... args) { this.info = info; + this.args = args; this.sig = sig; + this.name = null; + } + + /** + * Constructor. + * @param info input info + * @param name name of annotation + * @param args arguments + */ + public Ann(final InputInfo info, final QNm name, final Item... args) { + this.info = info; this.args = args; + this.name = name; + this.sig = null; } /** * Compares two annotations. - * @param an annotation to be compared + * @param ann annotation to be compared * @return result of check */ - public boolean eq(final Ann an) { + public boolean eq(final Ann ann) { final long as = args.length; - if(sig != an.sig || as != an.args.length) return false; + if(sig != null ? (sig != ann.sig) : (ann.name == null || !name.eq(ann.name))) return false; + if(as != ann.args.length) return false; for(int a = 0; a < as; a++) { - if(!args[a].sameAs(an.args[a])) return false; + if(!args[a].sameAs(ann.args[a])) return false; } return true; } @Override public String toString() { - final TokenBuilder tb = new TokenBuilder().add('%').add(sig.id()); + final TokenBuilder tb = new TokenBuilder().add('%'); + tb.add(sig == null ? name.prefixId(XQ_URI) : sig.id()); if(args.length != 0) tb.add('(').addSep(args, SEP).add(')'); return tb.add(' ').toString(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/collation/CollationItemSet.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/collation/CollationItemSet.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/collation/CollationItemSet.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/collation/CollationItemSet.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,8 +3,8 @@ import java.util.*; import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.util.hash.*; +import org.basex.query.util.list.*; import org.basex.query.value.item.*; import org.basex.util.*; @@ -16,7 +16,7 @@ */ public final class CollationItemSet implements ItemSet { /** Items. */ - private final ValueBuilder items = new ValueBuilder(); + private final ItemList items = new ItemList(); /** Collation. */ private final Collation coll; @@ -30,7 +30,7 @@ @Override public boolean add(final Item item, final InputInfo ii) throws QueryException { - final int is = (int) items.size(); + final int is = items.size(); for(int id = 0; id < is; id++) { if(items.get(id).equiv(item, coll, ii)) return false; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/DataBuilder.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/DataBuilder.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/DataBuilder.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/DataBuilder.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,6 +8,7 @@ import org.basex.data.*; import org.basex.query.iter.*; import org.basex.query.util.DataFTBuilder.DataFTMarker; +import org.basex.query.util.ft.*; import org.basex.query.util.list.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -15,7 +16,7 @@ import org.basex.util.*; /** - * Class for building memory-based database nodes. + * Data builder. Provides methods for copying XML nodes into a main-memory database instance. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen @@ -23,10 +24,8 @@ public final class DataBuilder { /** Target data instance. */ private final MemData data; - /** Full-text position data. */ + /** Full-text result builder. */ private DataFTBuilder ftbuilder; - /** Index reference of marker element. */ - private int name; /** * Constructor. @@ -44,13 +43,12 @@ * @return self reference */ public DataBuilder ftpos(final byte[] nm, final FTPosData pos, final int len) { - ftbuilder = new DataFTBuilder(pos, len); - name = data.elemNames.index(nm, null, false); + ftbuilder = new DataFTBuilder(pos, len, data.elemNames.index(nm, null, false)); return this; } /** - * Fills the data instance with the specified node. + * Adds database entries for the specified node. * @param node node */ public void build(final ANode node) { @@ -58,29 +56,27 @@ } /** - * Fills the data instance with the specified nodes. + * Adds database entries for the specified nodes. * @param nodes node list */ public void build(final ANodeList nodes) { data.meta.update(); - int ds = data.meta.size; - for(final ANode n : nodes) ds = addNode(n, ds, -1, null); + int next = data.meta.size; + for(final ANode n : nodes) next = addNode(n, next, -1); } /** - * Adds a fragment to a database instance. - * Document nodes are ignored. + * Adds a node. * @param node node to be added * @param pre node position * @param par node parent - * @param pNode parent of node to be added * @return pre value of next node */ - private int addNode(final ANode node, final int pre, final int par, final ANode pNode) { + private int addNode(final ANode node, final int pre, final int par) { switch(node.nodeType()) { case DOC: return addDoc(node, pre); case ELM: return addElem(node, pre, par); - case TXT: return addText(node, pre, par, pNode); + case TXT: return addText(node, pre, par); case ATT: return addAttr(node, pre, par); case COM: return addComm(node, pre, par); // will always be processing instruction @@ -95,15 +91,14 @@ * @return pre value of next node */ private int addDoc(final ANode node, final int pre) { - final int ds = data.meta.size; - final int s = size(node, false); - data.doc(ds, s, node.baseURI()); - data.insert(ds); - int p = pre + 1; - final AxisIter ai = node.children(); - for(ANode ch; (ch = ai.next()) != null;) p = addNode(ch, p, pre, null); - if(s != p - pre) data.size(ds, Data.DOC, p - pre); - return p; + final int last = data.meta.size; + final int size = size(node, false); + data.doc(last, size, node.baseURI()); + data.insert(last); + int next = pre + 1; + for(final ANode child : node.children()) next = addNode(child, next, pre); + if(size != next - pre) data.size(last, Data.DOC, next - pre); + return next; } /** @@ -111,22 +106,19 @@ * @param node node to be added * @param pre pre reference * @param par parent reference - * @return number of added nodes + * @return pre value of next node */ private int addAttr(final ANode node, final int pre, final int par) { - final int ds = data.meta.size; - final QNm q = node.qname(); - final byte[] uri = q.uri(); - int u = 0; - if(uri.length != 0) { - if(par == -1) data.nspaces.add(ds, pre + 1, q.prefix(), uri, data); - u = data.nspaces.uri(uri); - } - final int n = data.attrNames.index(q.string(), null, false); - // usually, attributes don't have a namespace flag. - // this is different here, because a stand-alone attribute has no parent element. - data.attr(ds, pre - par, n, node.string(), u, par == -1 && u != 0); - data.insert(ds); + final int last = data.meta.size; + final QNm qname = node.qname(); + final byte[] prefix = qname.prefix(), uri = qname.uri(); + // create new namespace entry if this is a prefixed and standalone attribute + final int uriId = uri.length == 0 || eq(prefix, XML) ? 0 : + par != -1 ? data.nspaces.uriId(uri) : data.nspaces.add(last, prefix, uri, data); + + final int nameId = data.attrNames.index(qname.string(), null, false); + data.attr(last, pre - par, nameId, node.string(), uriId); + data.insert(last); return pre + 1; } @@ -135,29 +127,24 @@ * @param node node to be added * @param pre pre reference * @param par parent reference - * @param pNode parent node * @return pre value of next node */ - private int addText(final ANode node, final int pre, final int par, final ANode pNode) { + private int addText(final ANode node, final int pre, final int par) { // check full-text mode int dist = pre - par; final ArrayList marks = ftbuilder != null ? ftbuilder.build(node) : null; - if(marks == null) return pre + addText(node.string(), dist); - - // adopt namespace from parent - ANode p = pNode; - while(p != null) { - final QNm n = p.qname(); - if(n != null && !n.hasPrefix()) break; - p = p.parent(); + if(marks == null) { + addText(node.string(), dist); + return pre + 1; } - final int u = p == null ? 0 : data.nspaces.uri(p.name(), true); + // adopt namespace from ancestor + final int uriId = data.nspaces.uriIdForPrefix(EMPTY, true); int ts = marks.size(); for(final DataFTMarker marker : marks) { if(marker.mark) { // open element - data.elem(dist++, name, 1, 2, u, false); + data.elem(dist++, ftbuilder.name(), 1, 2, uriId, false); data.insert(data.meta.size); ts++; } @@ -171,13 +158,11 @@ * Adds a text. * @param text text node * @param dist distance - * @return number of added nodes */ - private int addText(final byte[] text, final int dist) { - final int ds = data.meta.size; - data.text(ds, dist, text, Data.TEXT); - data.insert(ds); - return 1; + private void addText(final byte[] text, final int dist) { + final int last = data.meta.size; + data.text(last, dist, text, Data.TEXT); + data.insert(last); } /** @@ -185,13 +170,13 @@ * @param node node to be added * @param pre pre reference * @param par parent reference - * @return number of added nodes + * @return pre value of next node */ private int addPI(final ANode node, final int pre, final int par) { - final int ds = data.meta.size; - final byte[] v = trim(concat(node.name(), SPACE, node.string())); - data.text(ds, pre - par, v, Data.PI); - data.insert(ds); + final int last = data.meta.size; + final byte[] value = trim(concat(node.name(), SPACE, node.string())); + data.text(last, pre - par, value, Data.PI); + data.insert(last); return pre + 1; } @@ -200,12 +185,12 @@ * @param node node to be added * @param pre pre reference * @param par parent reference - * @return number of added nodes + * @return pre value of next node */ private int addComm(final ANode node, final int pre, final int par) { - final int ds = data.meta.size; - data.text(ds, pre - par, node.string(), Data.COMM); - data.insert(ds); + final int last = data.meta.size; + data.text(last, pre - par, node.string(), Data.COMM); + data.insert(last); return pre + 1; } @@ -217,58 +202,57 @@ * @return pre value of next node */ private int addElem(final ANode node, final int pre, final int par) { - final int ds = data.meta.size; + final int last = data.meta.size; // add new namespaces - data.nspaces.prepare(); final Atts ns = par == -1 ? node.nsScope(null) : node.namespaces(); - final int nl = ns.size(); - for(int n = 0; n < nl; n++) data.nspaces.add(ns.name(n), ns.value(n), ds); + data.nspaces.open(last, ns); - // analyze node name - final QNm nm = node.qname(); - final int tn = data.elemNames.index(nm.string(), null, false); - final int s = size(node, false); - final int u = data.nspaces.uri(nm.uri()); + // collect node name properties + final QNm qname = node.qname(); + final int size = size(node, false), asize = size(node, true); + final int nameId = data.elemNames.index(qname.string(), null, false); + final int uriId = data.nspaces.uriId(qname.uri()); // add element node - data.elem(pre - par, tn, size(node, true), s, u, nl != 0); - data.insert(ds); + data.elem(pre - par, nameId, asize, size, uriId, !ns.isEmpty()); + data.insert(last); + + // add attributes and child nodes + int cPre = pre + 1; + for(final ANode attr : node.attributes()) cPre = addAttr(attr, cPre, pre); + for(final ANode child : node.children()) cPre = addNode(child, cPre, pre); - // add attributes and children - int p = pre + 1; - AxisIter ai = node.attributes(); - for(ANode ch; (ch = ai.next()) != null;) p = addAttr(ch, p, pre); - ai = node.children(); - for(ANode ch; (ch = ai.next()) != null;) p = addNode(ch, p, pre, node); - data.nspaces.close(ds); + // finalize namespace structure + data.nspaces.close(last); // update size if additional nodes have been added by the descendants - if(s != p - pre) data.size(ds, Data.ELEM, p - pre); - return p; + if(size != cPre - pre) data.size(last, Data.ELEM, cPre - pre); + return cPre; } /** - * Determines the number of descendants of a fragment. + * Returns the number of descendants of a fragment, including the node itself. * @param node fragment node * @param att count attributes instead of elements * @return number of descendants + 1 or attribute size + 1 */ private static int size(final ANode node, final boolean att) { if(node instanceof DBNode) { - final DBNode dbn = (DBNode) node; - final int k = node.kind(); - return att ? dbn.data.attSize(dbn.pre, k) : dbn.data.size(dbn.pre, k); + final DBNode dbnode = (DBNode) node; + final Data data = dbnode.data(); + final int kind = node.kind(); + final int pre = dbnode.pre(); + return att ? data.attSize(pre, kind) : data.size(pre, kind); } - int s = 1; - AxisIter ai = node.attributes(); - while(ai.next() != null) ++s; + int size = 1; + BasicNodeIter iter = node.attributes(); + while(iter.next() != null) ++size; if(!att) { - ai = node.children(); - for(ANode i; (i = ai.next()) != null;) s += size(i, false); + for(final ANode child : node.children()) size += size(child, false); } - return s; + return size; } /** @@ -281,34 +265,34 @@ public static ANode stripNS(final ANode node, final byte[] ns, final Context ctx) { if(node.type != NodeType.ELM) return node; - final MemData md = new MemData(ctx.options); - final DataBuilder db = new DataBuilder(md); + final MemData data = new MemData(ctx.options); + final DataBuilder db = new DataBuilder(data); db.build(node); // flag indicating if namespace should be completely removed boolean del = true; // loop through all nodes - for(int pre = 0; pre < md.meta.size; pre++) { + for(int pre = 0; pre < data.meta.size; pre++) { // only check elements and attributes - final int kind = md.kind(pre); + final int kind = data.kind(pre); if(kind != Data.ELEM && kind != Data.ATTR) continue; // check if namespace is referenced - final byte[] uri = md.nspaces.uri(md.uri(pre, kind)); + final byte[] uri = data.nspaces.uri(data.uriId(pre, kind)); if(uri == null || !eq(uri, ns)) continue; - final byte[] nm = md.name(pre, kind); + final byte[] nm = data.name(pre, kind); if(prefix(nm).length == 0) { // no prefix: remove namespace from element if(kind == Data.ELEM) { - md.update(pre, Data.ELEM, nm, EMPTY); - md.nsFlag(pre, false); + data.update(pre, Data.ELEM, nm, EMPTY); + data.nsFlag(pre, false); } } else { // prefix: retain namespace del = false; } } - if(del) md.nspaces.delete(ns); - return new DBNode(md); + if(del) data.nspaces.delete(ns); + return new DBNode(data); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/DataFTBuilder.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/DataFTBuilder.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/DataFTBuilder.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/DataFTBuilder.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,13 +5,13 @@ import java.util.*; import org.basex.core.*; -import org.basex.data.*; +import org.basex.query.util.ft.*; import org.basex.query.value.node.*; import org.basex.util.*; import org.basex.util.ft.*; /** - * Class for constructing decorated full-text nodes. + * Constructor for marked full-text results. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen @@ -23,15 +23,28 @@ private final FTPosData pos; /** Length of full-text extract. */ private final int len; + /** Id of marker element name. */ + private final int name; /** * Constructor. * @param pos full-text position data * @param len length of extract + * @param name id of marker element name */ - DataFTBuilder(final FTPosData pos, final int len) { + DataFTBuilder(final FTPosData pos, final int len, final int name) { this.pos = pos; this.len = len; + this.name = name; + } + + + /** + * Returns the id of the marker element name. + * @return id + */ + int name() { + return name; } /** @@ -45,7 +58,7 @@ // not all nodes have full-text positions final DBNode dbnode = (DBNode) node; - final FTPos ftp = pos.get(dbnode.data, dbnode.pre); + final FTPos ftp = pos.get(dbnode.data(), dbnode.pre()); if(ftp == null) return null; final ArrayList marks = new ArrayList<>(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/Deep.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/Deep.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/Deep.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/Deep.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,869 +0,0 @@ -package org.basex.query.util.fingertree; - -import java.util.*; - -/** - * A deep node containing elements in the left and right digit and a sub-tree in - * the middle. - * - * @author BaseX Team 2005-15, BSD License - * @author Leo Woerteler - * - * @param node type - * @param element type - */ -final class Deep extends FingerTree { - /** Left digit. */ - final Node[] left; - /** Size of the left digit, cached for speeding up indexing. */ - final long leftSize; - /** Middle tree. */ - final FingerTree, E> middle; - /** Right digit. */ - final Node[] right; - - /** Size of this tree. */ - final long size; - - /** - * Constructor. - * @param left left digit - * @param leftSize size of the left digit - * @param middle middle tree - * @param right right digit - * @param size size of this tree - */ - Deep(final Node[] left, final long leftSize, - final FingerTree, E> middle, final Node[] right, final long size) { - this.left = left; - this.leftSize = leftSize; - this.middle = middle; - this.right = right; - this.size = size; - } - - /** - * Factory method calculating the size of the left digit. - * @param node type - * @param element type - * @param left left digit - * @param middle middle tree - * @param right right digit - * @param size size of this tree - * @return the deep node - */ - static Deep get(final Node[] left, final FingerTree, E> middle, - final Node[] right, final long size) { - return new Deep<>(left, size(left), middle, right, size); - } - - /** - * Factory method for deep nodes with an empty middle tree. - * @param node type - * @param element type - * @param left left digit - * @param leftSize size of the left sub-tree - * @param right right digit - * @param size size of this tree - * @return the deep node - */ - static Deep get(final Node[] left, final long leftSize, - final Node[] right, final long size) { - return new Deep<>(left, leftSize, Empty., E>getInstance(), right, size); - } - - /** - * Factory method for deep nodes with an empty middle tree calculating the size of the left digit. - * @param node type - * @param element type - * @param left left digit - * @param right right digit - * @param size size of this tree - * @return the deep node - */ - static Deep get(final Node[] left, final Node[] right, final long size) { - return new Deep<>(left, size(left), Empty., E>getInstance(), right, size); - } - - /** - * Factory method for deep nodes calculating all cached sizes. - * @param node type - * @param element type - * @param left left digit - * @param middle middle sub-tree - * @param right right digit - * @return the deep node - */ - static Deep get(final Node[] left, - final FingerTree, E> middle, final Node[] right) { - final long l = size(left), m = middle.size(), r = size(right); - return new Deep<>(left, l, middle, right, l + m + r); - } - - /** - * Factory method for deep nodes with empty middle tree calculating all cached sizes. - * @param node type - * @param element type - * @param left left digit - * @param right right digit - * @return the deep node - */ - static Deep get(final Node[] left, final Node[] right) { - final long l = size(left), r = size(right); - return new Deep<>(left, l, Empty., E>getInstance(), right, l + r); - } - - @Override - public Deep cons(final Node fst) { - if(left.length < 4) { - final Node[] newLeft = slice(left, -1, left.length); - newLeft[0] = fst; - return new Deep<>(newLeft, leftSize + fst.size(), middle, right, size + fst.size()); - } - - final Node[] newLeft = slice(left, -1, 1); - newLeft[0] = fst; - final FingerTree, E> mid = middle.cons(new InnerNode3<>(left[1], left[2], left[3])); - return Deep.get(newLeft, mid, right, size + fst.size()); - } - - @Override - public Deep snoc(final Node lst) { - if(right.length < 4) { - final Node[] newRight = slice(right, 0, right.length + 1); - newRight[right.length] = lst; - return new Deep<>(left, leftSize, middle, newRight, size + lst.size()); - } - - final FingerTree, E> mid = - middle.snoc(new InnerNode3<>(right[0], right[1], right[2])); - final Node[] newRight = slice(right, 3, 5); - newRight[1] = lst; - return new Deep<>(left, leftSize, mid, newRight, size + lst.size()); - } - - @Override - public Node head() { - return left[0]; - } - - @Override - public Node last() { - return right[right.length - 1]; - } - - @Override - public FingerTree init() { - final long newSize = size - right[right.length - 1].size(); - - if(right.length > 1) { - // right digit is safe, just shrink it - return new Deep<>(left, leftSize, middle, slice(right, 0, right.length - 1), newSize); - } - - if(middle.isEmpty()) { - // middle tree empty, make a tree from the left list - if(left.length == 1) return new Single<>(left[0]); - - final int mid = left.length / 2; - final Node[] newLeft = slice(left, 0, mid); - return Deep.get(newLeft, slice(left, mid, left.length), newSize); - } - - // extract values for the right digit from the middle - final InnerNode last = (InnerNode) middle.last(); - return new Deep<>(left, leftSize, middle.init(), last.copyChildren(), newSize); - } - - @Override - public FingerTree tail() { - final long fstSize = left[0].size(); - final long newSize = size - fstSize; - - if(left.length > 1) { - // left digit is safe, just shrink it - return new Deep<>(slice(left, 1, left.length), leftSize - fstSize, middle, right, newSize); - } - - if(middle.isEmpty()) { - // middle tree empty, make a tree from the right list - if(right.length == 1) return new Single<>(right[0]); - - final int mid = right.length / 2; - return Deep.get(slice(right, 0, mid), slice(right, mid, right.length), newSize); - } - - // extract values for the left digit from the middle - final InnerNode head = (InnerNode) middle.head(); - return new Deep<>(head.copyChildren(), head.size(), middle.tail(), right, newSize); - } - - @Override - public long size() { - return this.size; - } - - @Override - public Deep concat(final Node[] nodes, final FingerTree other) { - final Deep lft = (Deep) addAll(nodes, false); - if(!(other instanceof Deep)) { - return other instanceof Single ? lft.snoc(((Single) other).elem) : lft; - } - - final Deep rght = (Deep) other; - final Node[] as = lft.right, bs = rght.left; - final int l = as.length, n = l + bs.length, k = (n + 2) / 3; - @SuppressWarnings("unchecked") - final Node, E>[] out = new Node[k]; - int p = 0; - for(int i = 0; i < k; i++) { - final int rest = n - p; - final Node x = p < l ? as[p] : bs[p - l]; - p++; - final Node y = p < l ? as[p] : bs[p - l]; - p++; - if(rest > 4 || rest == 3) { - final Node z = p < l ? as[p] : bs[p - l]; - out[i] = new InnerNode3<>(x, y, z); - p++; - } else { - out[i] = new InnerNode2<>(x, y); - } - } - - final FingerTree, E> newMid = lft.middle.concat(out, rght.middle); - final long newSize = lft.leftSize + newMid.size() + rght.rightSize(); - return new Deep<>(lft.left, lft.leftSize, newMid, rght.right, newSize); - } - - @Override - public FingerTree reverse() { - final int l = left.length, r = right.length; - @SuppressWarnings("unchecked") - final Node[] newLeft = new Node[r], newRight = new Node[l]; - for(int i = 0; i < r; i++) newLeft[i] = right[r - 1 - i].reverse(); - for(int i = 0; i < l; i++) newRight[i] = left[l - 1 - i].reverse(); - return new Deep<>(newLeft, rightSize(), middle.reverse(), newRight, size); - } - - @Override - public FingerTree insert(final long pos, final E val) { - if(pos <= leftSize) { - // insert into left digit - int i = 0; - long p = pos; - for(;; i++) { - final long sub = left[i].size(); - if(p <= sub) break; - p -= sub; - } - - final int ll = left.length; - final Node l = i > 0 ? left[i - 1] : null, r = i + 1 < ll ? left[i + 1] : null; - @SuppressWarnings("unchecked") - final Node[] siblings = new Node[] { l, null, r, null }; - if(!left[i].insert(siblings, p, val)) { - // no split - final Node[] newLeft = left.clone(); - if(i > 0) newLeft[i - 1] = siblings[0]; - newLeft[i] = siblings[1]; - if(i + 1 < ll) newLeft[i + 1] = siblings[2]; - return new Deep<>(newLeft, leftSize + 1, middle, right, size + 1); - } - - // node was split - @SuppressWarnings("unchecked") - final Node[] temp = new Node[ll + 1]; - if(i > 0) { - System.arraycopy(left, 0, temp, 0, i - 1); - temp[i - 1] = siblings[0]; - } - temp[i] = siblings[1]; - temp[i + 1] = siblings[2]; - if(i + 1 < ll) { - temp[i + 2] = siblings[3]; - System.arraycopy(left, i + 2, temp, i + 3, ll - i - 2); - } - if(ll < 4) return new Deep<>(temp, leftSize + 1, middle, right, size + 1); - - // digit has to be split - final Node a = temp[0], b = temp[1]; - @SuppressWarnings("unchecked") - final Node[] newLeft = new Node[] { a, b }; - final Node, E> sub = new InnerNode3<>(temp[2], temp[3], temp[4]); - return new Deep<>(newLeft, a.size() + b.size(), middle.cons(sub), right, size + 1); - } - - long p = pos - leftSize; - final long midSize = middle.size(); - if(p < midSize) return new Deep<>(left, leftSize, middle.insert(p, val), right, size + 1); - - // insert into right digit - p -= midSize; - int i = 0; - for(;; i++) { - final long sub = right[i].size(); - if(p <= sub) break; - p -= sub; - } - - final int rl = right.length; - final Node l = i > 0 ? right[i - 1] : null, r = i + 1 < rl ? right[i + 1] : null; - @SuppressWarnings("unchecked") - final Node[] siblings = new Node[] { l, null, r, null }; - if(!right[i].insert(siblings, p, val)) { - // no split - final Node[] newRight = right.clone(); - if(i > 0) newRight[i - 1] = siblings[0]; - newRight[i] = siblings[1]; - if(i + 1 < rl) newRight[i + 1] = siblings[2]; - return new Deep<>(left, leftSize, middle, newRight, size + 1); - } - - // node was split - @SuppressWarnings("unchecked") - final Node[] temp = new Node[rl + 1]; - if(i > 0) { - System.arraycopy(right, 0, temp, 0, i - 1); - temp[i - 1] = siblings[0]; - } - temp[i] = siblings[1]; - temp[i + 1] = siblings[2]; - if(i + 1 < rl) { - temp[i + 2] = siblings[3]; - System.arraycopy(right, i + 2, temp, i + 3, rl - i - 2); - } - if(right.length < 4) return new Deep<>(left, leftSize, middle, temp, size + 1); - - // digit has to be split - final Node, E> sub = new InnerNode3<>(temp[0], temp[1], temp[2]); - final Node a = temp[3], b = temp[4]; - @SuppressWarnings("unchecked") - final Node[] newRight = new Node[] { a, b}; - return new Deep<>(left, leftSize, middle.snoc(sub), newRight, size + 1); - } - - @Override - public TreeSlice remove(final long pos) { - if(pos < leftSize) return new TreeSlice<>(removeLeft(pos)); - final long rightStart = leftSize + middle.size(); - if(pos >= rightStart) return new TreeSlice<>(removeRight(pos - rightStart)); - - final TreeSlice, E> slice = middle.remove(pos - leftSize); - if(slice.isTree()) { - // no underflow - final FingerTree, E> newMiddle = slice.getTree(); - return slice.setTree(new Deep<>(left, leftSize, newMiddle, right, size - 1)); - } - - // middle tree had an underflow, one sub-node left - final Node node = (Node) ((PartialInnerNode) slice.getPartial()).sub; - - // try to extend the smaller digit - if(left.length < right.length) { - // merge into left digit - final Node[] newLeft = slice(left, 0, left.length + 1); - newLeft[left.length] = node; - return slice.setTree(Deep.get(newLeft, leftSize + node.size(), right, size - 1)); - } - - if(right.length < 4) { - // merge into right digit - @SuppressWarnings("unchecked") - final Node[] newRight = new Node[right.length + 1]; - newRight[0] = node; - System.arraycopy(right, 0, newRight, 1, right.length); - return slice.setTree(Deep.get(left, leftSize, newRight, size - 1)); - } - - // redistribute the 9 nodes - final Node[] newLeft = slice(left, 0, 3); - final Node, E> newMid = new InnerNode3<>(left[3], node, right[0]); - final Node[] newRight = slice(right, 1, right.length); - return slice.setTree(Deep.get(newLeft, new Single<>(newMid), newRight, size - 1)); - } - - /** - * Remove an element from the left digit. - * @param pos position inside the left digit - * @return resulting tree - */ - private FingerTree removeLeft(final long pos) { - if(left.length > 1) { - // left digit cannot underflow, just delete the element - return new Deep<>(remove(left, pos), leftSize - 1, middle, right, size - 1); - } - - // singleton digit might underflow - final Node node = left[0]; - - if(!middle.isEmpty()) { - // next node for balancing is in middle tree - final InnerNode head = (InnerNode) middle.head(); - final Node first = head.getSub(0); - final Node[] rem = node.remove(null, first, pos); - final Node newNode = rem[1], newFirst = rem[2]; - - if(newNode == null) { - // nodes were merged - final Node[] newLeft = head.copyChildren(); - newLeft[0] = newFirst; - return Deep.get(newLeft, middle.tail(), right, size - 1); - } - - @SuppressWarnings("unchecked") - final Node[] newLeft = new Node[] { newNode }; - - if(newFirst != first) { - // nodes were balanced - final FingerTree, E> newMid = middle.replaceHead(head.replaceFirst(newFirst)); - return new Deep<>(newLeft, newNode.size(), newMid, right, size - 1); - } - - // no changes to this tree's structure - return new Deep<>(newLeft, newNode.size(), middle, right, size - 1); - } - - // potentially balance with right digit - final Node[] rem = node.remove(null, right[0], pos); - final Node newNode = rem[1], newFirstRight = rem[2]; - - if(newNode == null) { - // nodes were merged - if(right.length == 1) return new Single<>(newFirstRight); - final int mid = right.length / 2; - final Node[] newLeft = slice(right, 0, mid); - newLeft[0] = newFirstRight; - return Deep.get(newLeft, middle, slice(right, mid, right.length), size - 1); - } - - // structure does not change - @SuppressWarnings("unchecked") - final Node[] newLeft = new Node[] { newNode }; - - if(newFirstRight == right[0]) { - // right digit stays the same - return new Deep<>(newLeft, newLeft[0].size(), middle, right, size - 1); - } - - // adapt the right digit - final Node[] newRight = right.clone(); - newRight[0] = newFirstRight; - return new Deep<>(newLeft, newNode.size(), middle, newRight, size - 1); - } - - /** - * Remove an element from the right digit. - * @param pos position inside the right digit - * @return resulting tree - */ - private FingerTree removeRight(final long pos) { - if(right.length > 1) { - // right digit cannot underflow, just delete the element - return new Deep<>(left, leftSize, middle, remove(right, pos), size - 1); - } - - // singleton digit might underflow - final Node node = right[0]; - - if(!middle.isEmpty()) { - // potentially balance with middle tree - final InnerNode last = (InnerNode) middle.last(); - final Node lastSub = last.getSub(last.arity() - 1); - final Node[] rem = node.remove(lastSub, null, pos); - final Node newLastSub = rem[0], newNode = rem[1]; - - if(newNode == null) { - // nodes were merged - final Node[] newRight = last.copyChildren(); - newRight[newRight.length - 1] = newLastSub; - return new Deep<>(left, leftSize, middle.init(), newRight, size - 1); - } - - @SuppressWarnings("unchecked") - final Node[] newRight = new Node[] { newNode }; - - // replace last node in middle tree - final Node, E> newLast = last.replaceLast(newLastSub); - return new Deep<>(left, leftSize, middle.replaceLast(newLast), newRight, size - 1); - } - - // balance with left digit - final Node lastLeft = left[left.length - 1]; - final Node[] rem = node.remove(lastLeft, null, pos); - final Node newLastLeft = rem[0], newNode = rem[1]; - if(newNode == null) { - // nodes were merged - if(left.length == 1) { - // only one node left - return new Single<>(newLastLeft); - } - - @SuppressWarnings("unchecked") - final Node[] newRight = new Node[] { newLastLeft }; - return Deep.get(slice(left, 0, left.length - 1), newRight, size - 1); - } - - @SuppressWarnings("unchecked") - final Node[] newRight = new Node[] { newNode }; - - if(newLastLeft == lastLeft) { - // deletion could be absorbed - return Deep.get(left, leftSize, newRight, size - 1); - } - - // adapt the left digit - final Node[] newLeft = left.clone(); - newLeft[newLeft.length - 1] = newLastLeft; - return Deep.get(newLeft, newRight, size - 1); - } - - /** - * Deletes an element from the given digit containing at least two nodes. - * @param node type - * @param element type - * @param arr array of nodes - * @param pos deletion position - * @return new digit - */ - private static Node[] remove(final Node[] arr, final long pos) { - int i = 0; - long off = pos; - Node node; - for(;;) { - node = arr[i]; - final long nodeSize = node.size(); - if(off < nodeSize) break; - off -= nodeSize; - i++; - } - - final int n = arr.length; - final Node[] res = arr[i].remove(i == 0 ? null : arr[i - 1], - i == n - 1 ? null : arr[i + 1], off); - if(res[1] != null) { - // same number of nodes - final Node[] out = arr.clone(); - if(i > 0) out[i - 1] = res[0]; - out[i] = res[1]; - if(i < n - 1) out[i + 1] = res[2]; - return out; - } - - // the node was merged - @SuppressWarnings("unchecked") - final Node[] out = new Node[n - 1]; - if(i > 0) { - // nodes to the left - System.arraycopy(arr, 0, out, 0, i - 1); - out[i - 1] = res[0]; - } - - if(i < n - 1) { - // nodes to the right - out[i] = res[2]; - System.arraycopy(arr, i + 2, out, i + 1, n - i - 2); - } - - return out; - } - - @Override - public TreeSlice slice(final long from, final long len) { - if(from == 0 && len == size) return new TreeSlice<>(this); - final long midSize = middle.size(), rightOff = leftSize + midSize; - - final long inLeft = from + len <= leftSize ? len : from < leftSize ? leftSize - from : 0; - final long inRight = from >= rightOff ? len : from + len > rightOff ? from + len - rightOff : 0; - - @SuppressWarnings("unchecked") - final NodeLike[] buffer = new NodeLike[9]; - int inBuffer = splitDigit(left, from, inLeft, buffer, 0); - if(inLeft == len) { - final int n = inBuffer; - if(n == 1) { - if(buffer[0] instanceof PartialNode) return new TreeSlice<>((PartialNode) buffer[0]); - final Node node = (Node) buffer[0]; - return new TreeSlice<>(new Single<>(node)); - } - - final int mid1 = n / 2; - final Node[] ls = Deep.slice(buffer, 0, mid1), rs = Deep.slice(buffer, mid1, n); - return new TreeSlice<>(Deep.get(ls, rs, len)); - } - - final long inMiddle = len - inLeft - inRight; - final FingerTree, E> mid; - final TreeSlice, E> slice; - if(inMiddle == 0) { - mid = Empty.getInstance(); - slice = new TreeSlice<>(mid); - } else { - final long midOff = from <= leftSize ? 0 : from - leftSize; - slice = middle.slice(midOff, inMiddle); - if(!slice.isTree()) { - final NodeLike sub = ((PartialInnerNode) slice.getPartial()).sub; - inBuffer = sub.append(buffer, inBuffer); - mid = Empty.getInstance(); - } else { - mid = slice.getTree(); - } - } - - final long rightFrom = from < rightOff ? 0 : from - rightOff; - if(mid.isEmpty()) { - inBuffer = splitDigit(right, rightFrom, inRight, buffer, inBuffer); - return slice.setNodes(buffer, inBuffer, len); - } - - final FingerTree, E> mid2; - if(inBuffer > 1 || buffer[0] instanceof Node) { - mid2 = mid; - } else { - final InnerNode head = (InnerNode) mid.head(); - final int k = head.arity(); - inBuffer = head.getSub(0).append(buffer, inBuffer); - for(int i = 1; i < k; i++) buffer[inBuffer++] = head.getSub(i); - mid2 = mid.tail(); - } - - if(mid2.isEmpty()) { - inBuffer = splitDigit(right, rightFrom, inRight, buffer, inBuffer); - return slice.setNodes(buffer, inBuffer, len); - } - - final Node[] newLeft = slice(buffer, 0, inBuffer); - inBuffer = splitDigit(right, rightFrom, inRight, buffer, 0); - - final FingerTree, E> mid3; - final Node[] newRight; - if(inBuffer == 0) { - mid3 = mid2.init(); - newRight = ((InnerNode) mid2.last()).copyChildren(); - } else if(inBuffer > 1 || buffer[0] instanceof Node) { - mid3 = mid2; - newRight = slice(buffer, 0, inBuffer); - } else { - final NodeLike partial = buffer[0]; - final InnerNode last = (InnerNode) mid2.last(); - final int k = last.arity(); - for(int i = 0; i < k; i++) buffer[i] = last.getSub(i); - inBuffer = partial.append(buffer, k); - mid3 = mid2.init(); - newRight = slice(buffer, 0, inBuffer); - } - - return slice.setTree(Deep.get(newLeft, mid3, newRight, len)); - } - - /** - * Creates a tree slice from a digit. - * @param node type - * @param element type - * @param nodes the digit - * @param from element offset - * @param len number of elements - * @param buffer buffer to insert the node slice into - * @param inBuffer initial number of nodes in the buffer - * @return the slice - */ - private static int splitDigit(final Node[] nodes, final long from, - final long len, final NodeLike[] buffer, final int inBuffer) { - if(len <= 0) return inBuffer; - - // find the first sub-node containing used elements - int firstPos = 0; - long firstOff = from; - Node first = nodes[0]; - long firstSize = first.size(); - while(firstOff >= firstSize) { - firstOff -= firstSize; - first = nodes[++firstPos]; - firstSize = first.size(); - } - - // firstOff < firstSize - final long inFirst = firstSize - firstOff; - if(inFirst >= len) { - // everything in first sub-node - final NodeLike part = len == firstSize ? first : first.slice(firstOff, len); - return part.append(buffer, inBuffer); - } - - final NodeLike firstSlice = firstOff == 0 ? first : first.slice(firstOff, inFirst); - int numMerged = firstSlice.append(buffer, inBuffer); - - int pos = firstPos; - long remaining = len - inFirst; - while(remaining > 0) { - final Node curr = nodes[++pos]; - final long currSize = curr.size(); - final NodeLike slice = remaining >= currSize ? curr : curr.slice(0, remaining); - numMerged = slice.append(buffer, numMerged); - remaining -= currSize; - } - - return numMerged; - } - - @Override - public ListIterator listIterator(final boolean reverse) { - return new DeepTreeIterator<>(this, reverse); - } - - /** - * Calculates the size of the right digit. - * @return number of elements in the right digit - */ - private long rightSize() { - return size - leftSize - middle.size(); - } - - @Override - FingerTree addAll(final Node[] nodes, final boolean appendLeft) { - final int k = nodes.length; - if(k == 0) return this; - if(k == 1) return appendLeft ? cons(nodes[0]) : snoc(nodes[0]); - - if(appendLeft) { - int l = k + left.length; - final Node[] ls = slice(nodes, 0, l); - System.arraycopy(left, 0, ls, k, left.length); - if(l <= 4) return Deep.get(ls, middle, right); - FingerTree, E> newMid = middle; - for(; l > 4; l -= 3) { - final InnerNode sub = new InnerNode3<>(ls[l - 3], ls[l - 2], ls[l - 1]); - newMid = newMid.cons(sub); - } - return Deep.get(slice(ls, 0, l), newMid, right); - } - - final int r = right.length + k; - final Node[] rs = slice(right, 0, r); - System.arraycopy(nodes, 0, rs, right.length, k); - if(k + right.length <= 4) return Deep.get(left, middle, rs); - FingerTree, E> newMid = middle; - int i = 0; - for(; r - i > 4; i += 3) { - final InnerNode sub = new InnerNode3<>(rs[i], rs[i + 1], rs[i + 2]); - newMid = newMid.snoc(sub); - } - return Deep.get(left, newMid, slice(rs, i, r)); - } - - @Override - public FingerTree replaceHead(final Node head) { - final long sizeDiff = head.size() - left[0].size(); - final Node[] newLeft = left.clone(); - newLeft[0] = head; - return new Deep<>(newLeft, leftSize + sizeDiff, middle, right, size + sizeDiff); - } - - @Override - public FingerTree replaceLast(final Node last) { - final int lst = right.length - 1; - final Node[] newRight = right.clone(); - newRight[lst] = last; - return new Deep<>(left, leftSize, middle, newRight, size + last.size() - - right[lst].size()); - } - - @Override - void toString(final StringBuilder sb, final int indent) { - for(int i = 0; i < indent; i++) - sb.append(" "); - sb.append("Deep[\n"); - - // left digit - for(int i = 0; i < indent + 1; i++) - sb.append(" "); - sb.append("Left[\n"); - for(final Node e : left) { - e.toString(sb, indent + 2); - sb.append('\n'); - } - for(int i = 0; i < indent + 1; i++) - sb.append(" "); - sb.append("]\n"); - - // middle tree - middle.toString(sb, indent + 1); - sb.append('\n'); - - // right digit - for(int i = 0; i < indent + 1; i++) - sb.append(" "); - sb.append("Right[\n"); - for(final Node e : right) { - e.toString(sb, indent + 2); - sb.append("\n"); - } - for(int i = 0; i < indent + 1; i++) - sb.append(" "); - sb.append("]\n"); - - for(int i = 0; i < indent; i++) - sb.append(" "); - sb.append("]"); - } - - @Override - public long checkInvariants() { - if(left.length < 1 || left.length > 4) throw new AssertionError( - "Wrong left digit length: " + left.length); - long sz = 0; - for(final Node nd : left) - sz += nd.checkInvariants(); - if(sz != leftSize) throw new AssertionError("Wrong leftSize: " + leftSize + " vs. " - + sz); - sz += middle.checkInvariants(); - if(right.length < 1 || right.length > 4) throw new AssertionError( - "Wrong right digit length: " + right.length); - for(final Node nd : right) - sz += nd.checkInvariants(); - if(sz != size) throw new AssertionError("Wrong size: " + size + " vs. " + sz); - return sz; - } - - /** - * Calculates the size of a digit. - * @param node type - * @param arr digit - * @return size - */ - static > long size(final N[] arr) { - long size = 0; - for(final N o : arr) - size += o.size(); - return size; - } - - /** - * Returns an array containing the values at the indices {@code from} to {@code to - 1} - * in the given array. Its length is always {@code to - from}. If {@code from} is - * smaller than zero, the first {@code -from} entries in the resulting array are - * {@code null}. If {@code to > arr.length} then the last {@code to - arr.length} - * entries are {@code null}. - * @param node type - * @param element type - * @param arr input array - * @param from first index, inclusive (may be negative) - * @param to last index, exclusive (may be greater than {@code arr.length}) - * @return resulting array - */ - static Node[] slice(final NodeLike[] arr, final int from, final int to) { - @SuppressWarnings("unchecked") - final Node[] out = new Node[to - from]; - final int in0 = Math.max(0, from), in1 = Math.min(to, arr.length); - final int out0 = Math.max(-from, 0); - System.arraycopy(arr, in0, out, out0, in1 - in0); - return out; - } - - @Override - public long[] sizes(final int depth) { - final long[] sizes = middle.sizes(depth + 1); - sizes[depth] = size - middle.size(); - return sizes; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/DeepTreeIterator.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/DeepTreeIterator.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/DeepTreeIterator.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/DeepTreeIterator.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,275 +0,0 @@ -package org.basex.query.util.fingertree; - -import java.util.*; - -/** - * Iterator over a finger tree with at least two sub-nodes. - * - * @author BaseX Team 2005-15, BSD License - * @author Leo Woerteler - * - * @param element type - */ -final class DeepTreeIterator implements ListIterator { - /** Current index. */ - private long index; - - /** Stack of deep trees. */ - private Deep[] trees; - /** Position of the current node in the digits. */ - private int deepPos; - /** Stack pointer for the deep trees. */ - private int tTop; - - /** Node stack. */ - private InnerNode[] nodes; - /** Position stack. */ - private int[] poss; - /** Stack pointer. */ - private int nTop; - - /** Current leaf node. */ - private Node leaf; - /** Position inside the current leaf. */ - private int leafPos; - - /** - * Constructor. - * @param root root node - * @param reverse flag for starting at the end - */ - @SuppressWarnings("unchecked") - DeepTreeIterator(final Deep root, final boolean reverse) { - index = reverse ? root.size : 0; - - trees = new Deep[8]; - trees[0] = root; - tTop = 0; - - nodes = new InnerNode[8]; - poss = new int[8]; - nTop = -1; - - Node curr = reverse ? root.right[root.right.length - 1] : root.left[0]; - while(curr instanceof InnerNode) { - final InnerNode inner = (InnerNode) curr; - final int idx = reverse ? inner.arity() - 1 : 0; - if(++nTop == nodes.length) { - nodes = Arrays.copyOf(nodes, 2 * nodes.length); - poss = Arrays.copyOf(poss, 2 * poss.length); - } - nodes[nTop] = inner; - poss[nTop] = idx; - curr = inner.getSub(idx); - } - - deepPos = reverse ? root.right.length : -root.left.length; - leaf = (Node) curr; - leafPos = reverse ? curr.arity() : 0; - } - - @Override - public int nextIndex() { - return (int) index; - } - - @Override - public boolean hasNext() { - return index < trees[0].size; - } - - @Override - @SuppressWarnings("unchecked") - public E next() { - if(index >= trees[0].size) throw new NoSuchElementException(); - - index++; - if(leafPos < leaf.arity()) return leaf.getSub(leafPos++); - - // leaf drained, backtrack - while(nTop >= 0 && poss[nTop] == nodes[nTop].arity() - 1) nTop--; - - final Node start; - if(nTop >= 0) { - // go to next sub-node - start = nodes[nTop].getSub(++poss[nTop]); - } else { - // node drained, move to the next one - final Deep curr = trees[tTop]; - if(deepPos < -1) { - // go to next node in digit - ++deepPos; - start = curr.left[curr.left.length + deepPos]; - } else if(deepPos == -1) { - // left digit drained - final FingerTree mid = curr.middle; - if(mid instanceof Empty) { - // skip empty middle tree - deepPos = 1; - start = curr.right[0]; - } else if(mid instanceof Single) { - // iterate through the one middle node - deepPos = 0; - start = ((Single) mid).elem; - } else { - final Deep deep = (Deep) mid; - if(++tTop == trees.length) { - final Deep[] newTrees = new Deep[2 * tTop]; - System.arraycopy(trees, 0, newTrees, 0, tTop); - trees = newTrees; - } - trees[tTop] = deep; - deepPos = -deep.left.length; - start = deep.left[0]; - } - } else if(deepPos == 0) { - // we are in a single middle node - deepPos = 1; - start = curr.right[0]; - } else { - // we are in the right digit - final int p = deepPos - 1; - if(p < curr.right.length - 1) { - // go to next node in digit - deepPos++; - start = curr.right[p + 1]; - } else { - // backtrack one level - trees[tTop] = null; - deepPos = 0; - tTop--; - start = trees[tTop].right[0]; - deepPos = 1; - } - } - } - - Node sub = start; - while(sub instanceof InnerNode) { - if(++nTop == nodes.length) { - final InnerNode[] newNodes = new InnerNode[2 * nTop]; - System.arraycopy(nodes, 0, newNodes, 0, nTop); - nodes = newNodes; - poss = Arrays.copyOf(poss, 2 * nTop); - } - nodes[nTop] = (InnerNode) sub; - poss[nTop] = 0; - sub = nodes[nTop].getSub(0); - } - - leaf = (Node) sub; - leafPos = 1; - return leaf.getSub(0); - } - - @Override - public int previousIndex() { - return (int) (index - 1); - } - - @Override - public boolean hasPrevious() { - return index > 0; - } - - @Override - @SuppressWarnings("unchecked") - public E previous() { - if(index <= 0) throw new NoSuchElementException(); - - --index; - if(leafPos > 0) return leaf.getSub(--leafPos); - - // leaf drained, backtrack - while(nTop >= 0 && poss[nTop] == 0) nTop--; - - final Node start; - if(nTop >= 0) { - // go to previous sub-node - start = nodes[nTop].getSub(--poss[nTop]); - } else { - // node drained, move to the previous one - final Deep curr = trees[tTop]; - if(deepPos > 1) { - // go to next node in right digit - --deepPos; - start = curr.right[deepPos - 1]; - } else if(deepPos == 1) { - // right digit drained - final FingerTree mid = curr.middle; - if(mid instanceof Empty) { - // skip empty middle tree - final int l = curr.left.length; - start = curr.left[l - 1]; - deepPos = -1; - } else if(mid instanceof Single) { - // iterate through the one middle node - start = ((Single) mid).elem; - deepPos = 0; - } else { - // go into the middle tree - final Deep deep = (Deep) mid; - if(++tTop == trees.length) { - final Deep[] newTrees = new Deep[2 * tTop]; - System.arraycopy(trees, 0, newTrees, 0, tTop); - trees = newTrees; - } - trees[tTop] = deep; - final int r = deep.right.length; - start = deep.right[r - 1]; - deepPos = r; - } - } else if(deepPos == 0) { - start = curr.left[curr.left.length - 1]; - deepPos = -1; - } else { - // we are in the left digit - final int l = curr.left.length, p = l + deepPos; - if(p > 0) { - // go to previous node in digit - --deepPos; - start = curr.left[p - 1]; - } else { - // backtrack one level - trees[tTop] = null; - --tTop; - final Node[] left = trees[tTop].left; - start = left[left.length - 1]; - deepPos = -1; - } - } - } - - Node sub = start; - while(sub instanceof InnerNode) { - if(++nTop == nodes.length) { - final InnerNode[] newNodes = new InnerNode[2 * nTop]; - System.arraycopy(nodes, 0, newNodes, 0, nTop); - nodes = newNodes; - poss = Arrays.copyOf(poss, 2 * nTop); - } - nodes[nTop] = (InnerNode) sub; - poss[nTop] = sub.arity() - 1; - sub = nodes[nTop].getSub(poss[nTop]); - } - - leaf = (Node) sub; - leafPos = sub.arity() - 1; - return leaf.getSub(leafPos); - } - - @Override - public void set(final E e) { - throw new UnsupportedOperationException(); - } - - @Override - public void add(final E e) { - throw new UnsupportedOperationException(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/DeepTree.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/DeepTree.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/DeepTree.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/DeepTree.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,858 @@ +package org.basex.query.util.fingertree; + +/** + * A deep node containing elements in the left and right digit and a sub-tree in + * the middle. + * + * @author BaseX Team 2005-15, BSD License + * @author Leo Woerteler + * + * @param node type + * @param element type + */ +final class DeepTree extends FingerTree { + /** Preferred size of an inner node. */ + private static final int NODE_SIZE = MAX_ARITY; + /** Left digit. */ + final Node[] left; + /** Size of the left digit, cached for speeding up indexing. */ + final long leftSize; + /** Middle tree. */ + final FingerTree, E> middle; + /** Right digit. */ + final Node[] right; + + /** Size of this tree. */ + final long size; + + /** + * Constructor. + * @param left left digit + * @param leftSize size of the left digit + * @param middle middle tree + * @param right right digit + * @param size size of this tree + */ + DeepTree(final Node[] left, final long leftSize, + final FingerTree, E> middle, final Node[] right, final long size) { + this.left = left; + this.leftSize = leftSize; + this.middle = middle; + this.right = right; + this.size = size; + assert left.length > 0 && right.length > 0 + && size == leftSize + middle.size() + size(right); + } + + /** + * Factory method calculating the size of the left digit. + * @param node type + * @param element type + * @param left left digit + * @param middle middle tree + * @param right right digit + * @param size size of this tree + * @return the deep node + */ + static DeepTree get(final Node[] left, final FingerTree, E> middle, + final Node[] right, final long size) { + return new DeepTree<>(left, size(left), middle, right, size); + } + + /** + * Factory method for deep nodes with an empty middle tree. + * @param node type + * @param element type + * @param left left digit + * @param leftSize size of the left sub-tree + * @param right right digit + * @param size size of this tree + * @return the deep node + */ + static DeepTree get(final Node[] left, final long leftSize, + final Node[] right, final long size) { + return new DeepTree<>(left, leftSize, EmptyTree., E>getInstance(), right, size); + } + + /** + * Factory method for deep nodes with an empty middle tree calculating the size of the left digit. + * @param node type + * @param element type + * @param left left digit + * @param right right digit + * @param size size of this tree + * @return the deep node + */ + static DeepTree get(final Node[] left, final Node[] right, + final long size) { + return new DeepTree<>(left, size(left), EmptyTree., E>getInstance(), right, size); + } + + /** + * Factory method for deep nodes calculating all cached sizes. + * @param node type + * @param element type + * @param left left digit + * @param middle middle sub-tree + * @param right right digit + * @return the deep node + */ + static DeepTree get(final Node[] left, + final FingerTree, E> middle, final Node[] right) { + final long l = size(left), m = middle.size(), r = size(right); + return new DeepTree<>(left, l, middle, right, l + m + r); + } + + /** + * Factory method for deep nodes with empty middle tree calculating all cached sizes. + * @param node type + * @param element type + * @param left left digit + * @param right right digit + * @return the deep node + */ + static DeepTree get(final Node[] left, final Node[] right) { + final long l = size(left), r = size(right); + return new DeepTree<>(left, l, EmptyTree., E>getInstance(), right, l + r); + } + + @Override + public DeepTree cons(final Node fst) { + final long sz = fst.size(); + if(left.length < MAX_DIGIT) { + final Node[] newLeft = slice(left, -1, left.length); + newLeft[0] = fst; + return new DeepTree<>(newLeft, leftSize + sz, middle, right, size + sz); + } + + final int ll = left.length, m = ll - NODE_SIZE; + final Node[] newLeft = slice(left, -1, m), sub = slice(left, m, ll); + newLeft[0] = fst; + final FingerTree, E> mid = middle.cons(new InnerNode<>(sub)); + return DeepTree.get(newLeft, mid, right, size + sz); + } + + @Override + public DeepTree snoc(final Node lst) { + if(right.length < MAX_DIGIT) { + final Node[] newRight = slice(right, 0, right.length + 1); + newRight[right.length] = lst; + return new DeepTree<>(left, leftSize, middle, newRight, size + lst.size()); + } + + final int rl = right.length, m = NODE_SIZE; + final Node[] sub = slice(right, 0, m), newRight = slice(right, m, rl + 1); + newRight[rl - m] = lst; + final FingerTree, E> mid = middle.snoc(new InnerNode<>(sub)); + return new DeepTree<>(left, leftSize, mid, newRight, size + lst.size()); + } + + @Override + public Node head() { + return left[0]; + } + + @Override + public Node last() { + return right[right.length - 1]; + } + + @Override + public FingerTree init() { + final long newSize = size - right[right.length - 1].size(); + + if(right.length > 1) { + // right digit is safe, just shrink it + return new DeepTree<>(left, leftSize, middle, slice(right, 0, right.length - 1), newSize); + } + + if(middle.isEmpty()) { + // middle tree empty, make a tree from the left list + if(left.length == 1) return new SingletonTree<>(left[0]); + + final int mid = left.length / 2; + return DeepTree.get(slice(left, 0, mid), slice(left, mid, left.length), newSize); + } + + // extract values for the right digit from the middle + final InnerNode last = (InnerNode) middle.last(); + return new DeepTree<>(left, leftSize, middle.init(), last.children, newSize); + } + + @Override + public FingerTree tail() { + final long fstSize = left[0].size(), newSize = size - fstSize; + + if(left.length > 1) { + // left digit is safe, just shrink it + final Node[] newLeft = slice(left, 1, left.length); + return new DeepTree<>(newLeft, leftSize - fstSize, middle, right, newSize); + } + + if(middle.isEmpty()) { + // middle tree empty, make a tree from the right list + if(right.length == 1) return new SingletonTree<>(right[0]); + + final int mid = right.length / 2; + return DeepTree.get(slice(right, 0, mid), slice(right, mid, right.length), newSize); + } + + // extract values for the left digit from the middle + final InnerNode head = (InnerNode) middle.head(); + return new DeepTree<>(head.children, head.size(), middle.tail(), right, newSize); + } + + @Override + public long size() { + return this.size; + } + + @Override + public DeepTree concat(final Node[] nodes, final long sz, + final FingerTree other) { + final DeepTree lft = (DeepTree) addAll(nodes, sz, false); + if(!(other instanceof DeepTree)) return other.isEmpty() ? lft : lft.snoc(other.head()); + + final DeepTree rght = (DeepTree) other; + final Node[] as = lft.right, bs = rght.left; + final int l = as.length, n = l + bs.length, k = (n + MAX_ARITY - 1) / MAX_ARITY; + @SuppressWarnings("unchecked") + final Node, E>[] out = new Node[k]; + for(int i = 0, p = 0; i < k; i++) { + final int rem = k - i, curr = (n - p + rem - 1) / rem; + @SuppressWarnings("unchecked") + final Node[] ch = new Node[curr]; + final int inL = l - p; + if(curr <= inL) { + System.arraycopy(as, p, ch, 0, curr); + } else if(inL > 0) { + System.arraycopy(as, p, ch, 0, inL); + System.arraycopy(bs, 0, ch, inL, curr - inL); + } else { + System.arraycopy(bs, -inL, ch, 0, curr); + } + out[i] = new InnerNode<>(ch); + p += curr; + } + + final long inMid = lft.rightSize() + rght.leftSize; + final FingerTree, E> newMid = lft.middle.concat(out, inMid, rght.middle); + final long newSize = lft.leftSize + newMid.size() + rght.rightSize(); + return new DeepTree<>(lft.left, lft.leftSize, newMid, rght.right, newSize); + } + + @Override + public FingerTree reverse() { + final int l = left.length, r = right.length; + @SuppressWarnings("unchecked") + final Node[] newLeft = new Node[r], newRight = new Node[l]; + for(int i = 0; i < r; i++) newLeft[i] = right[r - 1 - i].reverse(); + for(int i = 0; i < l; i++) newRight[i] = left[l - 1 - i].reverse(); + return new DeepTree<>(newLeft, rightSize(), middle.reverse(), newRight, size); + } + + @Override + public FingerTree insert(final long pos, final E val) { + if(pos <= leftSize) { + // insert into left digit + int i = 0; + long p = pos; + for(;; i++) { + final long sub = left[i].size(); + if(p <= sub) break; + p -= sub; + } + + final int ll = left.length; + final Node l = i > 0 ? left[i - 1] : null, r = i + 1 < ll ? left[i + 1] : null; + @SuppressWarnings("unchecked") + final Node[] siblings = new Node[] { l, null, r, null }; + if(!left[i].insert(siblings, p, val)) { + // no split + final Node[] newLeft = left.clone(); + if(i > 0) newLeft[i - 1] = siblings[0]; + newLeft[i] = siblings[1]; + if(i + 1 < ll) newLeft[i + 1] = siblings[2]; + return new DeepTree<>(newLeft, leftSize + 1, middle, right, size + 1); + } + + // node was split + @SuppressWarnings("unchecked") + final Node[] temp = new Node[ll + 1]; + if(i > 0) { + System.arraycopy(left, 0, temp, 0, i - 1); + temp[i - 1] = siblings[0]; + } + temp[i] = siblings[1]; + temp[i + 1] = siblings[2]; + if(i + 1 < ll) { + temp[i + 2] = siblings[3]; + System.arraycopy(left, i + 2, temp, i + 3, ll - i - 2); + } + if(ll < MAX_DIGIT) return new DeepTree<>(temp, leftSize + 1, middle, right, size + 1); + + // digit has to be split + final int m = temp.length - NODE_SIZE; + final Node[] newLeft = slice(temp, 0, m), ch = slice(temp, m, temp.length); + return DeepTree.get(newLeft, middle.cons(new InnerNode<>(ch)), right, size + 1); + } + + long p = pos - leftSize; + final long midSize = middle.size(); + if(p < midSize) return new DeepTree<>(left, leftSize, middle.insert(p, val), right, size + 1); + + // insert into right digit + p -= midSize; + int i = 0; + for(;; i++) { + final long sub = right[i].size(); + if(p <= sub) break; + p -= sub; + } + + final int rl = right.length; + final Node l = i > 0 ? right[i - 1] : null, r = i + 1 < rl ? right[i + 1] : null; + @SuppressWarnings("unchecked") + final Node[] siblings = new Node[] { l, null, r, null }; + if(!right[i].insert(siblings, p, val)) { + // no split + final Node[] newRight = right.clone(); + if(i > 0) newRight[i - 1] = siblings[0]; + newRight[i] = siblings[1]; + if(i + 1 < rl) newRight[i + 1] = siblings[2]; + return new DeepTree<>(left, leftSize, middle, newRight, size + 1); + } + + // node was split + @SuppressWarnings("unchecked") + final Node[] temp = new Node[rl + 1]; + if(i > 0) { + System.arraycopy(right, 0, temp, 0, i - 1); + temp[i - 1] = siblings[0]; + } + temp[i] = siblings[1]; + temp[i + 1] = siblings[2]; + if(i + 1 < rl) { + temp[i + 2] = siblings[3]; + System.arraycopy(right, i + 2, temp, i + 3, rl - i - 2); + } + if(right.length < MAX_DIGIT) return new DeepTree<>(left, leftSize, middle, temp, size + 1); + + // digit has to be split + final int m = NODE_SIZE; + final Node[] ch = slice(temp, 0, m), newRight = slice(temp, m, temp.length); + return new DeepTree<>(left, leftSize, middle.snoc(new InnerNode<>(ch)), newRight, size + 1); + } + + @Override + public TreeSlice remove(final long pos) { + if(pos < leftSize) return new TreeSlice<>(removeLeft(pos)); + final long rightStart = leftSize + middle.size(); + if(pos >= rightStart) return new TreeSlice<>(removeRight(pos - rightStart)); + + final TreeSlice, E> slice = middle.remove(pos - leftSize); + if(slice.isTree()) { + // no underflow + final FingerTree, E> newMiddle = slice.getTree(); + return slice.setTree(new DeepTree<>(left, leftSize, newMiddle, right, size - 1)); + } + + // middle tree had an underflow, one sub-node left + final Node node = (Node) ((PartialInnerNode) slice.getPartial()).sub; + + // try to extend the smaller digit + if(left.length < right.length) { + // merge into left digit + final Node[] newLeft = slice(left, 0, left.length + 1); + newLeft[left.length] = node; + return slice.setTree(DeepTree.get(newLeft, leftSize + node.size(), right, size - 1)); + } + + if(right.length < MAX_DIGIT) { + // merge into right digit + final Node[] newRight = slice(right, -1, right.length); + newRight[0] = node; + return slice.setTree(DeepTree.get(left, leftSize, newRight, size - 1)); + } + + // redistribute the nodes + final int n = 2 * MAX_DIGIT + 1, ll = (n - NODE_SIZE) / 2; + @SuppressWarnings("unchecked") + final Node[] newLeft = slice(left, 0, ll), ch = new Node[NODE_SIZE]; + final int inL = left.length - ll, inR = NODE_SIZE - inL - 1; + System.arraycopy(left, ll, ch, 0, inL); + ch[inL] = node; + System.arraycopy(right, 0, ch, inL + 1, inR); + final Node[] newRight = slice(right, inR, MAX_DIGIT); + final Node, E> newMid = new InnerNode<>(ch); + return slice.setTree(DeepTree.get(newLeft, new SingletonTree<>(newMid), newRight, size - 1)); + } + + /** + * Remove an element from the left digit. + * @param pos position inside the left digit + * @return resulting tree + */ + private FingerTree removeLeft(final long pos) { + if(left.length > 1) { + // left digit cannot underflow, just delete the element + return new DeepTree<>(remove(left, pos), leftSize - 1, middle, right, size - 1); + } + + // singleton digit might underflow + final Node node = left[0]; + + if(!middle.isEmpty()) { + // next node for balancing is in middle tree + final InnerNode head = (InnerNode) middle.head(); + final Node first = head.getSub(0); + final NodeLike[] rem = node.remove(null, first, pos); + final Node newNode = (Node) rem[1], newFirst = (Node) rem[2]; + + if(newNode == null) { + // nodes were merged + final Node[] newLeft = head.children.clone(); + newLeft[0] = newFirst; + return DeepTree.get(newLeft, middle.tail(), right, size - 1); + } + + @SuppressWarnings("unchecked") + final Node[] newLeft = new Node[] { newNode }; + + if(newFirst != first) { + // nodes were balanced + final FingerTree, E> newMid = middle.replaceHead(head.replaceFirst(newFirst)); + return new DeepTree<>(newLeft, newNode.size(), newMid, right, size - 1); + } + + // no changes to this tree's structure + return new DeepTree<>(newLeft, newNode.size(), middle, right, size - 1); + } + + // potentially balance with right digit + final NodeLike[] rem = node.remove(null, right[0], pos); + final Node newNode = (Node) rem[1], newFirstRight = (Node) rem[2]; + + if(newNode == null) { + // nodes were merged + if(right.length == 1) return new SingletonTree<>(newFirstRight); + final int mid = right.length / 2; + final Node[] newLeft = slice(right, 0, mid); + newLeft[0] = newFirstRight; + return DeepTree.get(newLeft, middle, slice(right, mid, right.length), size - 1); + } + + // structure does not change + @SuppressWarnings("unchecked") + final Node[] newLeft = new Node[] { newNode }; + + if(newFirstRight == right[0]) { + // right digit stays the same + return new DeepTree<>(newLeft, newLeft[0].size(), middle, right, size - 1); + } + + // adapt the right digit + final Node[] newRight = right.clone(); + newRight[0] = newFirstRight; + return new DeepTree<>(newLeft, newNode.size(), middle, newRight, size - 1); + } + + /** + * Remove an element from the right digit. + * @param pos position inside the right digit + * @return resulting tree + */ + private FingerTree removeRight(final long pos) { + if(right.length > 1) { + // right digit cannot underflow, just delete the element + return new DeepTree<>(left, leftSize, middle, remove(right, pos), size - 1); + } + + // singleton digit might underflow + final Node node = right[0]; + + if(!middle.isEmpty()) { + // potentially balance with middle tree + final InnerNode last = (InnerNode) middle.last(); + final Node lastSub = last.getSub(last.arity() - 1); + final NodeLike[] rem = node.remove(lastSub, null, pos); + final Node newLastSub = (Node) rem[0], newNode = (Node) rem[1]; + + if(newNode == null) { + // nodes were merged + final Node[] newRight = last.children.clone(); + newRight[newRight.length - 1] = newLastSub; + return new DeepTree<>(left, leftSize, middle.init(), newRight, size - 1); + } + + @SuppressWarnings("unchecked") + final Node[] newRight = new Node[] { newNode }; + + // replace last node in middle tree + final Node, E> newLast = last.replaceLast(newLastSub); + return new DeepTree<>(left, leftSize, middle.replaceLast(newLast), newRight, size - 1); + } + + // balance with left digit + final Node lastLeft = left[left.length - 1]; + final NodeLike[] rem = node.remove(lastLeft, null, pos); + final Node newLastLeft = (Node) rem[0], newNode = (Node) rem[1]; + if(newNode == null) { + // nodes were merged + if(left.length == 1) { + // only one node left + return new SingletonTree<>(newLastLeft); + } + + @SuppressWarnings("unchecked") + final Node[] newRight = new Node[] { newLastLeft }; + return DeepTree.get(slice(left, 0, left.length - 1), newRight, size - 1); + } + + @SuppressWarnings("unchecked") + final Node[] newRight = new Node[] { newNode }; + + if(newLastLeft == lastLeft) { + // deletion could be absorbed + return DeepTree.get(left, leftSize, newRight, size - 1); + } + + // adapt the left digit + final Node[] newLeft = left.clone(); + newLeft[newLeft.length - 1] = newLastLeft; + return DeepTree.get(newLeft, newRight, size - 1); + } + + /** + * Deletes an element from the given digit containing at least two nodes. + * @param node type + * @param element type + * @param arr array of nodes + * @param pos deletion position + * @return new digit + */ + private static Node[] remove(final Node[] arr, final long pos) { + int i = 0; + long off = pos; + Node node; + for(;;) { + node = arr[i]; + final long nodeSize = node.size(); + if(off < nodeSize) break; + off -= nodeSize; + i++; + } + + final int n = arr.length; + final NodeLike[] res = arr[i].remove( + i == 0 ? null : arr[i - 1], i == n - 1 ? null : arr[i + 1], off); + final Node l = (Node) res[0], m = (Node) res[1], r = (Node) res[2]; + if(m != null) { + // same number of nodes + final Node[] out = arr.clone(); + if(i > 0) out[i - 1] = l; + out[i] = m; + if(i < n - 1) out[i + 1] = r; + return out; + } + + // the node was merged + @SuppressWarnings("unchecked") + final Node[] out = new Node[n - 1]; + if(i > 0) { + // nodes to the left + System.arraycopy(arr, 0, out, 0, i - 1); + out[i - 1] = l; + } + + if(i < n - 1) { + // nodes to the right + out[i] = r; + System.arraycopy(arr, i + 2, out, i + 1, n - i - 2); + } + + return out; + } + + @Override + public TreeSlice slice(final long from, final long len) { + if(from == 0 && len == size) return new TreeSlice<>(this); + final long midSize = middle.size(), rightOff = leftSize + midSize; + + final long inLeft = from + len <= leftSize ? len : from < leftSize ? leftSize - from : 0; + final long inRight = from >= rightOff ? len : from + len > rightOff ? from + len - rightOff : 0; + + @SuppressWarnings("unchecked") + final NodeLike[] buffer = new NodeLike[2 * MAX_DIGIT + 1]; + int inBuffer = splitDigit(left, from, inLeft, buffer, 0); + if(inLeft == len) { + final int n = inBuffer; + if(n == 1) return new TreeSlice<>(buffer[0]); + final int mid1 = n / 2; + final Node[] ls = slice(buffer, 0, mid1), rs = slice(buffer, mid1, n); + return new TreeSlice<>(DeepTree.get(ls, rs, len)); + } + + final long inMiddle = len - inLeft - inRight; + final FingerTree, E> mid; + final TreeSlice, E> slice; + if(inMiddle == 0) { + mid = EmptyTree.getInstance(); + slice = new TreeSlice<>(mid); + } else { + final long midOff = from <= leftSize ? 0 : from - leftSize; + slice = middle.slice(midOff, inMiddle); + if(!slice.isTree()) { + final NodeLike sub = ((PartialInnerNode) slice.getPartial()).sub; + inBuffer = sub.append(buffer, inBuffer); + mid = EmptyTree.getInstance(); + } else { + mid = slice.getTree(); + } + } + + final long rightFrom = from < rightOff ? 0 : from - rightOff; + if(mid.isEmpty()) { + inBuffer = splitDigit(right, rightFrom, inRight, buffer, inBuffer); + return slice.setNodes(buffer, inBuffer, len); + } + + final FingerTree, E> mid2; + if(inBuffer > 1 || buffer[0] instanceof Node) { + mid2 = mid; + } else { + final InnerNode head = (InnerNode) mid.head(); + final int k = head.arity(); + inBuffer = head.getSub(0).append(buffer, inBuffer); + for(int i = 1; i < k; i++) buffer[inBuffer++] = head.getSub(i); + mid2 = mid.tail(); + } + + if(mid2.isEmpty()) { + inBuffer = splitDigit(right, rightFrom, inRight, buffer, inBuffer); + return slice.setNodes(buffer, inBuffer, len); + } + + final Node[] newLeft = slice(buffer, 0, inBuffer); + inBuffer = splitDigit(right, rightFrom, inRight, buffer, 0); + + final FingerTree, E> mid3; + final Node[] newRight; + if(inBuffer == 0) { + mid3 = mid2.init(); + newRight = ((InnerNode) mid2.last()).children; + } else if(inBuffer > 1 || buffer[0] instanceof Node) { + mid3 = mid2; + newRight = slice(buffer, 0, inBuffer); + } else { + final NodeLike partial = buffer[0]; + final InnerNode last = (InnerNode) mid2.last(); + final int k = last.arity(); + for(int i = 0; i < k; i++) buffer[i] = last.getSub(i); + inBuffer = partial.append(buffer, k); + mid3 = mid2.init(); + newRight = slice(buffer, 0, inBuffer); + } + + return slice.setTree(DeepTree.get(newLeft, mid3, newRight, len)); + } + + /** + * Creates a tree slice from a digit. + * @param node type + * @param element type + * @param nodes the digit + * @param from element offset + * @param len number of elements + * @param buffer buffer to insert the node slice into + * @param inBuffer initial number of nodes in the buffer + * @return the slice + */ + private static int splitDigit(final Node[] nodes, final long from, + final long len, final NodeLike[] buffer, final int inBuffer) { + if(len <= 0) return inBuffer; + + // find the first sub-node containing used elements + int firstPos = 0; + long firstOff = from; + Node first = nodes[0]; + long firstSize = first.size(); + while(firstOff >= firstSize) { + firstOff -= firstSize; + first = nodes[++firstPos]; + firstSize = first.size(); + } + + // firstOff < firstSize + final long inFirst = firstSize - firstOff; + if(inFirst >= len) { + // everything in first sub-node + final NodeLike part = len == firstSize ? first : first.slice(firstOff, len); + return part.append(buffer, inBuffer); + } + + final NodeLike firstSlice = firstOff == 0 ? first : first.slice(firstOff, inFirst); + int numMerged = firstSlice.append(buffer, inBuffer); + + int pos = firstPos; + long remaining = len - inFirst; + while(remaining > 0) { + final Node curr = nodes[++pos]; + final long currSize = curr.size(); + final NodeLike slice = remaining >= currSize ? curr : curr.slice(0, remaining); + numMerged = slice.append(buffer, numMerged); + remaining -= currSize; + } + + return numMerged; + } + + /** + * Calculates the size of the right digit. + * @return number of elements in the right digit + */ + private long rightSize() { + return size - leftSize - middle.size(); + } + + @Override + FingerTree addAll(final Node[] nodes, final long sz, final boolean appendLeft) { + final int k = nodes.length; + if(k == 0) return this; + if(k == 1) return appendLeft ? cons(nodes[0]) : snoc(nodes[0]); + + if(appendLeft) { + int l = k + left.length; + final Node[] ls = slice(nodes, 0, l); + System.arraycopy(left, 0, ls, k, left.length); + if(l <= MAX_DIGIT) return DeepTree.get(ls, middle, right); + + FingerTree, E> newMid = middle; + for(int rem = (l + MAX_ARITY - 1) / MAX_ARITY; rem > 1; rem--) { + final int curr = (l + rem - 1) / rem; + newMid = newMid.cons(new InnerNode<>(slice(ls, l - curr, l))); + l -= curr; + } + + return DeepTree.get(slice(ls, 0, l), newMid, right); + } + + final int r = right.length + k; + final Node[] rs = slice(right, 0, r); + System.arraycopy(nodes, 0, rs, right.length, k); + if(k + right.length <= MAX_DIGIT) return DeepTree.get(left, middle, rs); + + int i = 0; + FingerTree, E> newMid = middle; + for(int rem = (r + MAX_ARITY - 1) / MAX_ARITY; rem > 1; rem--) { + final int curr = (r - i + rem - 1) / rem; + newMid = newMid.snoc(new InnerNode<>(slice(rs, i, i + curr))); + i += curr; + } + + return DeepTree.get(left, newMid, slice(rs, i, r)); + } + + @Override + public FingerTree replaceHead(final Node head) { + final long sizeDiff = head.size() - left[0].size(); + final Node[] newLeft = left.clone(); + newLeft[0] = head; + return new DeepTree<>(newLeft, leftSize + sizeDiff, middle, right, size + sizeDiff); + } + + @Override + public FingerTree replaceLast(final Node last) { + final int lst = right.length - 1; + final Node[] newRight = right.clone(); + newRight[lst] = last; + return new DeepTree<>(left, leftSize, middle, newRight, size + last.size() + - right[lst].size()); + } + + @Override + void toString(final StringBuilder sb, final int indent) { + for(int i = 0; i < indent; i++) sb.append(" "); + sb.append("Deep(").append(size).append(")[\n"); + + // left digit + for(int i = 0; i < indent + 1; i++) sb.append(" "); + sb.append("Left(").append(leftSize).append(")[\n"); + for(final Node e : left) { + toString(e, sb, indent + 2); + sb.append('\n'); + } + for(int i = 0; i < indent + 1; i++) sb.append(" "); + sb.append("]\n"); + + // middle tree + middle.toString(sb, indent + 1); + sb.append('\n'); + + // right digit + for(int i = 0; i < indent + 1; i++) sb.append(" "); + sb.append("Right[\n"); + for(final Node e : right) { + toString(e, sb, indent + 2); + sb.append("\n"); + } + for(int i = 0; i < indent + 1; i++) sb.append(" "); + sb.append("]\n"); + + for(int i = 0; i < indent; i++) sb.append(" "); + sb.append("]"); + } + + @Override + public long checkInvariants() { + if(left.length < 1 || left.length > MAX_DIGIT) throw new AssertionError( + "Wrong left digit length: " + left.length); + long sz = 0; + for(final Node nd : left) + sz += nd.checkInvariants(); + if(sz != leftSize) throw new AssertionError("Wrong leftSize: " + leftSize + " vs. " + + sz); + sz += middle.checkInvariants(); + if(right.length < 1 || right.length > MAX_DIGIT) throw new AssertionError( + "Wrong right digit length: " + right.length); + for(final Node nd : right) + sz += nd.checkInvariants(); + if(sz != size) throw new AssertionError("Wrong size: " + size + " vs. " + sz); + return sz; + } + + /** + * Calculates the size of a digit. + * @param node type + * @param arr digit + * @return size + */ + static > long size(final N[] arr) { + long size = 0; + for(final N o : arr) + size += o.size(); + return size; + } + + /** + * Returns an array containing the values at the indices {@code from} to {@code to - 1} + * in the given array. Its length is always {@code to - from}. If {@code from} is + * smaller than zero, the first {@code -from} entries in the resulting array are + * {@code null}. If {@code to > arr.length} then the last {@code to - arr.length} + * entries are {@code null}. + * @param node type + * @param element type + * @param arr input array + * @param from first index, inclusive (may be negative) + * @param to last index, exclusive (may be greater than {@code arr.length}) + * @return resulting array + */ + static Node[] slice(final NodeLike[] arr, final int from, final int to) { + @SuppressWarnings("unchecked") + final Node[] out = new Node[to - from]; + final int in0 = Math.max(0, from), in1 = Math.min(to, arr.length); + final int out0 = Math.max(-from, 0); + System.arraycopy(arr, in0, out, out0, in1 - in0); + return out; + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/Empty.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/Empty.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/Empty.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/Empty.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,129 +0,0 @@ -package org.basex.query.util.fingertree; - -import java.util.*; - -/** - * An empty finger tree. - * - * @author BaseX Team 2005-15, BSD License - * @author Leo Woerteler - * - * @param node type - * @param element type - */ -final class Empty extends FingerTree { - /** The empty finger tree. */ - static final Empty INSTANCE = new Empty<>(); - - /** - * Getter for the empty finger tree. - * @param node type - * @param element type - * @return empty tree - */ - @SuppressWarnings("unchecked") - static Empty getInstance() { - return (Empty) INSTANCE; - } - - /** Hidden constructor. */ - private Empty() { - } - - @Override - public FingerTree cons(final Node fst) { - return new Single<>(fst); - } - - @Override - public FingerTree snoc(final Node lst) { - return new Single<>(lst); - } - - @Override - public Node head() { - throw new NoSuchElementException(); - } - - @Override - public Node last() { - throw new NoSuchElementException(); - } - - @Override - public FingerTree init() { - throw new IllegalStateException("Empty Tree"); - } - - @Override - public FingerTree tail() { - throw new IllegalStateException("Empty Tree"); - } - - @Override - public long size() { - return 0; - } - - @Override - public FingerTree concat(final Node[] nodes, final FingerTree other) { - return other.addAll(nodes, true); - } - - @Override - public TreeSlice slice(final long pos, final long len) { - if(pos == 0 && len == 0) return new TreeSlice<>(this); - throw new AssertionError("Empty sub-tree."); - } - - @Override - public FingerTree reverse() { - return this; - } - - @Override - public FingerTree insert(final long pos, final E val) { - throw new AssertionError("Empty sub-tree."); - } - - @Override - public TreeSlice remove(final long pos) { - throw new AssertionError("Empty sub-tree."); - } - - @Override - public ListIterator listIterator(final boolean reverse) { - return Collections.emptyListIterator(); - } - - @Override - public FingerTree replaceHead(final Node head) { - throw new AssertionError("Empty sub-tree."); - } - - @Override - public FingerTree replaceLast(final Node last) { - throw new AssertionError("Empty sub-tree."); - } - - @Override - void toString(final StringBuilder sb, final int indent) { - for(int i = 0; i < indent; i++) sb.append(" "); - sb.append("Empty[]"); - } - - @Override - public long checkInvariants() { - return 0; - } - - @Override - public long[] sizes(final int depth) { - return new long[depth]; - } - - @Override - FingerTree addAll(final Node[] nodes, final boolean left) { - return buildTree(nodes, nodes.length, Deep.size(nodes)); - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/EmptyTree.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/EmptyTree.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/EmptyTree.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/EmptyTree.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,120 @@ +package org.basex.query.util.fingertree; + +import java.util.*; + +/** + * An empty finger tree. + * + * @author BaseX Team 2005-15, BSD License + * @author Leo Woerteler + * + * @param node type + * @param element type + */ +final class EmptyTree extends FingerTree { + /** The empty finger tree. */ + static final EmptyTree INSTANCE = new EmptyTree<>(); + + /** + * Getter for the empty finger tree. + * @param node type + * @param element type + * @return empty tree + */ + @SuppressWarnings("unchecked") + static EmptyTree getInstance() { + return (EmptyTree) INSTANCE; + } + + /** Hidden constructor. */ + private EmptyTree() { + } + + @Override + public FingerTree cons(final Node fst) { + return new SingletonTree<>(fst); + } + + @Override + public FingerTree snoc(final Node lst) { + return new SingletonTree<>(lst); + } + + @Override + public Node head() { + throw new NoSuchElementException(); + } + + @Override + public Node last() { + throw new NoSuchElementException(); + } + + @Override + public FingerTree init() { + throw new IllegalStateException("Empty Tree"); + } + + @Override + public FingerTree tail() { + throw new IllegalStateException("Empty Tree"); + } + + @Override + public long size() { + return 0; + } + + @Override + public FingerTree concat(final Node[] nodes, final long sz, + final FingerTree other) { + return other.addAll(nodes, sz, true); + } + + @Override + public TreeSlice slice(final long pos, final long len) { + if(pos == 0 && len == 0) return new TreeSlice<>(this); + throw new AssertionError("Empty sub-tree."); + } + + @Override + public FingerTree reverse() { + return this; + } + + @Override + public FingerTree insert(final long pos, final E val) { + throw new AssertionError("Empty sub-tree."); + } + + @Override + public TreeSlice remove(final long pos) { + throw new AssertionError("Empty sub-tree."); + } + + @Override + public FingerTree replaceHead(final Node head) { + throw new AssertionError("Empty sub-tree."); + } + + @Override + public FingerTree replaceLast(final Node last) { + throw new AssertionError("Empty sub-tree."); + } + + @Override + void toString(final StringBuilder sb, final int indent) { + for(int i = 0; i < indent; i++) sb.append(" "); + sb.append("Empty[]"); + } + + @Override + public long checkInvariants() { + return 0; + } + + @Override + FingerTree addAll(final Node[] nodes, final long sz, final boolean left) { + return buildTree(nodes, nodes.length, sz); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/FingerTreeBuilder.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/FingerTreeBuilder.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/FingerTreeBuilder.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/FingerTreeBuilder.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,5 +1,7 @@ package org.basex.query.util.fingertree; +import java.util.*; + /** * A builder for {@link FingerTree}s from leaf nodes. * @@ -8,9 +10,10 @@ * * @param element type */ -public final class FingerTreeBuilder { +@SuppressWarnings("unchecked") +public final class FingerTreeBuilder implements Iterable { /** The root node, {@code null} if the tree is empty. */ - private BufferNode root; + private Object root; /** * Checks if this builder is empty, i.e. if no leaf nodes were added to it. @@ -27,8 +30,12 @@ public void prepend(final Node leaf) { if(root == null) { root = new BufferNode<>(leaf); + } else if(root instanceof BufferNode) { + ((BufferNode) root).prepend(leaf); } else { - root.prepend(leaf); + final BufferNode newRoot = new BufferNode<>((FingerTree) root); + newRoot.prepend(leaf); + root = newRoot; } } @@ -39,8 +46,12 @@ public void append(final Node leaf) { if(root == null) { root = new BufferNode<>(leaf); + } else if(root instanceof BufferNode) { + ((BufferNode) root).append(leaf); } else { - root.append(leaf); + final BufferNode newRoot = new BufferNode<>((FingerTree) root); + newRoot.append(leaf); + root = newRoot; } } @@ -52,8 +63,12 @@ if(!tree.isEmpty()) { if(root == null) { root = new BufferNode<>(tree); + } else if(root instanceof BufferNode) { + ((BufferNode) root).append(tree); } else { - root.append(tree); + final BufferNode newRoot = new BufferNode<>((FingerTree) root); + newRoot.append(tree); + root = newRoot; } } } @@ -63,24 +78,28 @@ * @return the resulting finger tree */ public FingerTree freeze() { - return root == null ? FingerTree.empty() : root.freeze(); - } - - /** - * Writes the elements contained in this builder onto the given string builder. - * @param sb string builder - */ - public void toString(final StringBuilder sb) { - if(root != null) root.toString(sb); + return root == null ? FingerTree.empty() : + root instanceof BufferNode ? ((BufferNode) root).freeze() : (FingerTree) root; } @Override public String toString() { final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('['); - toString(sb); + final Iterator iter = iterator(); + if(iter.hasNext()) { + sb.append(iter.next()); + while(iter.hasNext()) sb.append(", ").append(iter.next()); + } return sb.append(']').toString(); } + @Override + public Iterator iterator() { + if(root == null) return Collections.emptyIterator(); + if(root instanceof FingerTree) return ((FingerTree) root).iterator(); + return new BufferNodeIterator<>((BufferNode) root); + } + /** * Node of the middle tree. * @@ -88,17 +107,25 @@ * @param element type */ private static class BufferNode { + /** Size of inner nodes to create. */ + private static final int NODE_SIZE = FingerTree.MAX_ARITY; + /** Maximum number of elements in a digit. */ + private static final int MAX_DIGIT = NODE_SIZE + 1; + /** Maximum number of nodes in the digits. */ + private static final int CAP = 2 * MAX_DIGIT; /** Ring buffer for nodes in the digits. */ - @SuppressWarnings("unchecked") - final Node[] nodes = new Node[8]; + final Node[] nodes = new Node[CAP]; /** Number of elements in left digit. */ - int l; + int inLeft; /** Position of middle between left and right digit in buffer. */ - int m = 4; + int midPos = MAX_DIGIT; /** Number of elements in right digit. */ - int r; - /** Root node of middle tree. */ - BufferNode, E> sub; + int inRight; + /** + * Root node of middle tree, either a {@code FingerTree, E>} or a + * {@code BufferNode, E>}. + */ + Object middle; /** * Constructs a buffered tree containing the given single node. @@ -113,13 +140,13 @@ * @param tree the tree to take the contents of */ BufferNode(final FingerTree tree) { - if(tree instanceof Single) { - prepend(((Single) tree).elem); + if(tree instanceof SingletonTree) { + prepend(((SingletonTree) tree).elem); } else { - final Deep deep = (Deep) tree; + final DeepTree deep = (DeepTree) tree; for(int i = deep.left.length; --i >= 0;) prepend(deep.left[i]); final FingerTree, E> mid = deep.middle; - if(!mid.isEmpty()) sub = new BufferNode<>(mid); + if(!mid.isEmpty()) middle = mid; for(final Node node : deep.right) append(node); } } @@ -129,26 +156,21 @@ * @param node the node to add */ void prepend(final Node node) { - if(l < 4) { - nodes[(m - l + 7) & 7] = node; - l++; - } else if(sub == null && r < 4) { - m = (m + 7) & 7; - nodes[(m - l + 8) & 7] = node; - r++; + if(inLeft < MAX_DIGIT) { + nodes[(midPos - inLeft - 1 + CAP) % CAP] = node; + inLeft++; + } else if(middle == null && inRight < MAX_DIGIT) { + midPos = (midPos - 1 + CAP) % CAP; + nodes[(midPos - inLeft + CAP) % CAP] = node; + inRight++; } else { - final int l3 = (m + 7) & 7, l2 = (l3 + 7) & 7, l1 = (l2 + 7) & 7, l0 = (l1 + 7) & 7; - final Node, E> next = new InnerNode3<>(nodes[l1], nodes[l2], nodes[l3]); - nodes[l3] = nodes[l0]; - nodes[l2] = node; - nodes[l1] = null; - nodes[l0] = null; - l = 2; - if(sub == null) { - sub = new BufferNode<>(next); - } else { - sub.prepend(next); - } + final int l = (midPos - inLeft + CAP) % CAP; + final Node, E> next = new InnerNode<>(copy(l + 1, inLeft - 1)); + nodes[(midPos - 1 + CAP) % CAP] = nodes[l]; + nodes[(midPos - 2 + CAP) % CAP] = node; + inLeft = 2; + if(middle == null) middle = new BufferNode<>(next); + else midBuffer().prepend(next); } } @@ -157,26 +179,20 @@ * @param node the node to add */ void append(final Node node) { - if(r < 4) { - nodes[(m + r) & 7] = node; - r++; - } else if(sub == null && l < 4) { - m = (m + 1) & 7; - nodes[(m + r - 1) & 7] = node; - l++; + if(inRight < MAX_DIGIT) { + nodes[(midPos + inRight) % CAP] = node; + inRight++; + } else if(middle == null && inLeft < MAX_DIGIT) { + midPos = (midPos + 1) % CAP; + nodes[(midPos + inRight - 1) % CAP] = node; + inLeft++; } else { - final int r0 = m, r1 = (r0 + 1) & 7, r2 = (r1 + 1) & 7, r3 = (r2 + 1) & 7; - final Node, E> next = new InnerNode3<>(nodes[r0], nodes[r1], nodes[r2]); - nodes[r0] = nodes[r3]; - nodes[r1] = node; - nodes[r2] = null; - nodes[r3] = null; - r = 2; - if(sub == null) { - sub = new BufferNode<>(next); - } else { - sub.append(next); - } + final Node, E> next = new InnerNode<>(copy(midPos, inRight - 1)); + nodes[midPos] = nodes[(midPos + inRight - 1) % CAP]; + nodes[(midPos + 1) % CAP] = node; + inRight = 2; + if(middle == null) middle = new BufferNode<>(next); + else midBuffer().append(next); } } @@ -185,53 +201,48 @@ * @param tree finger tree to append */ void append(final FingerTree tree) { - if(!(tree instanceof Deep)) { - if(tree instanceof Single) append(((Single) tree).elem); + if(!(tree instanceof DeepTree)) { + if(tree instanceof SingletonTree) append(((SingletonTree) tree).elem); return; } - final Deep deep = (Deep) tree; + final DeepTree deep = (DeepTree) tree; final Node[] ls = deep.left, rs = deep.right; final int ll = ls.length, rl = rs.length; final FingerTree, E> mid = deep.middle; if(mid.isEmpty()) { + // add digits for(int i = 0; i < ll; i++) append(ls[i]); for(int i = 0; i < rl; i++) append(rs[i]); - } else if(sub == null) { - final int n = l + r; - @SuppressWarnings("unchecked") + } else if(middle == null) { + // cache previous contents and re-add them afterwards + final int n = inLeft + inRight; final Node[] buff = new Node[n + ll]; - for(int i = 0; i < n; i++) buff[i] = nodes[(m - l + i + 8) & 7]; + copyInto(midPos - inLeft, buff, 0, n); System.arraycopy(ls, 0, buff, n, ll); - l = r = 0; - sub = new BufferNode<>(mid); + inLeft = inRight = 0; + middle = mid; for(int i = buff.length; --i >= 0;) prepend(buff[i]); for(int i = 0; i < rl; i++) append(rs[i]); } else { - final int k = r + ll; - @SuppressWarnings("unchecked") - final Node[] buff = new Node[k]; - for(int i = 0; i < r; i++) { - final int j = (m + i) & 7; - buff[i] = nodes[j]; - nodes[j] = null; - } - System.arraycopy(ls, 0, buff, r, ll); - r = 0; - - for(int i = 0; i < k;) { - final int rest = k - i; - if(rest > 4 || rest == 3) { - sub.append(new InnerNode3<>(buff[i], buff[i + 1], buff[i + 2])); - i += 3; - } else { - sub.append(new InnerNode2<>(buff[i], buff[i + 1])); - i += 2; - } + // inner digits have to be merged + final int n = inRight + ll; + final Node[] buff = new Node[n]; + copyInto(midPos, buff, 0, inRight); + System.arraycopy(ls, 0, buff, inRight, ll); + inRight = 0; + for(int k = (n + NODE_SIZE - 1) / NODE_SIZE, p = 0; k > 0; k--) { + final int inNode = (n - p + k - 1) / k; + final Node[] out = new Node[inNode]; + System.arraycopy(buff, p, out, 0, inNode); + final Node, E> sub = new InnerNode<>(out); + if(middle == null) middle = new BufferNode<>(sub); + else midBuffer().append(sub); + p += inNode; } - - sub.append(mid); + if(middle == null) middle = mid; + else midBuffer().append(mid); for(int i = 0; i < rl; i++) append(rs[i]); } } @@ -241,44 +252,155 @@ * @return the finger tree */ FingerTree freeze() { - final int n = l + r; - if(n == 1) return new Single<>(nodes[(m + r + 7) & 7]); - final int a = sub == null ? n / 2 : l, b = n - a; - @SuppressWarnings("unchecked") - final Node[] left = new Node[a], right = new Node[b]; - final int lOff = m - l + 8, rOff = lOff + a; - for(int i = 0; i < a; i++) left[i] = nodes[(lOff + i) & 7]; - for(int i = 0; i < b; i++) right[i] = nodes[(rOff + i) & 7]; - return sub == null ? Deep.get(left, right) : Deep.get(left, sub.freeze(), right); + final int n = inLeft + inRight; + if(n == 1) return new SingletonTree<>(nodes[(midPos + inRight - 1 + CAP) % CAP]); + final int a = middle == null ? n / 2 : inLeft, l = midPos - inLeft; + final Node[] left = copy(l, a), right = copy(l + a, n - a); + if(middle == null) return DeepTree.get(left, right); + + if(middle instanceof FingerTree) { + final FingerTree, E> tree = (FingerTree, E>) middle; + return DeepTree.get(left, tree, right); + } + + final BufferNode, E> buffer = (BufferNode, E>) middle; + return DeepTree.get(left, buffer.freeze(), right); } /** - * Writes the elements contained in this node onto the given string builder. - * @param sb string builder - */ - void toString(final StringBuilder sb) { - boolean first = true; - for(int i = 0; i < l; i++) { - final Node node = nodes[(m - l + i + 8) & 7]; - for(final E elem : node) { - if(first) first = false; - else sb.append(", "); - sb.append(elem); - } + * Returns the node at the given position in this node's ring buffer. + * @param pos position + * @return node at that position + */ + Node get(final int pos) { + return nodes[(((midPos + pos) % CAP) + CAP) % CAP]; + } + + /** + * Returns the middle tree as a buffer node. + * @return middle buffer node + */ + private BufferNode, E> midBuffer() { + if(middle == null) return null; + if(middle instanceof BufferNode) return (BufferNode, E>) middle; + final BufferNode, E> mid = new BufferNode<>((FingerTree, E>) middle); + middle = mid; + return mid; + } + + /** + * Copies the elements in the given range from the ring buffer into an array. + * @param start start of the range + * @param len length of the range + * @return array containing all nodes in the range + */ + private Node[] copy(final int start, final int len) { + final Node[] out = new Node[len]; + copyInto(start, out, 0, len); + return out; + } + + /** + * Copies the nodes in the given range of the ring buffer into the given array. + * @param start start position of the range in the ring buffer + * @param arr output array + * @param pos start position in the output array + * @param len length of the range + */ + private void copyInto(final int start, final Node[] arr, final int pos, final int len) { + final int p = ((start % CAP) + CAP) % CAP, k = CAP - p; + if(len <= k) { + System.arraycopy(nodes, p, arr, pos, len); + } else { + System.arraycopy(nodes, p, arr, pos, k); + System.arraycopy(nodes, 0, arr, pos + k, len - k); } - if(sub != null) { - if(first) first = false; - else sb.append(", "); - sub.toString(sb); - } - for(int i = 0; i < r; i++) { - final Node node = nodes[(m + i) & 7]; - for(final E elem : node) { - if(first) first = false; - else sb.append(", "); - sb.append(elem); + } + } + + /** + * Iterator over the elements in this builder. + * @param element type + */ + private static class BufferNodeIterator implements Iterator { + /** Stack of buffer nodes. */ + private BufferNode[] stack = new BufferNode[8]; + /** Stack of position inside the buffer nodes. */ + private int[] poss = new int[8]; + /** Stack top. */ + private int top; + /** Iterator over the current tree node. */ + private Iterator sub; + + /** + * Constructor. + * @param root buffer node + */ + BufferNodeIterator(final BufferNode root) { + stack[0] = root; + final int pos = -root.inLeft; + poss[0] = pos; + sub = new FingerTreeIterator<>(root.get(pos), 0); + } + + @Override + public boolean hasNext() { + return sub != null; + } + + @Override + public E next() { + if(sub == null) throw new NoSuchElementException(); + final E out = sub.next(); + if(sub.hasNext()) return out; + + // sub-iterator empty + sub = null; + final BufferNode buffer = stack[top]; + poss[top]++; + + if(poss[top] < 0) { + sub = new FingerTreeIterator<>(buffer.get(poss[top]), 0); + return out; + } + + if(poss[top] == 0) { + final Object mid = buffer.middle; + if(mid != null) { + if(mid instanceof FingerTree) { + sub = ((FingerTree) mid).iterator(); + } else { + final BufferNode buff = (BufferNode) mid; + if(++top == stack.length) { + stack = Arrays.copyOf(stack, 2 * top); + poss = Arrays.copyOf(poss, 2 * top); + } + stack[top] = buff; + poss[top] = -buff.inLeft; + sub = new FingerTreeIterator<>(buff.get(poss[top]), 0); + } + return out; } + poss[top]++; } + + if(poss[top] <= buffer.inRight) { + sub = new FingerTreeIterator<>(buffer.get(poss[top] - 1), 0); + return out; + } + + stack[top] = null; + if(--top >= 0) { + sub = new FingerTreeIterator<>(stack[top].get(0), 0); + poss[top]++; + } + + return out; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); } } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/FingerTreeIterator.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/FingerTreeIterator.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/FingerTreeIterator.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/FingerTreeIterator.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,386 @@ +package org.basex.query.util.fingertree; + +import java.util.*; + +/** + * List iterator over the elements of a finger tree. + * + * @author BaseX Team 2005-15, BSD License + * @author Leo Woerteler + * + * @param element type + */ +final class FingerTreeIterator implements ListIterator { + /** Size of the root. */ + private final long n; + /** Current index. */ + private long index; + + /** Stack of deep trees. */ + private DeepTree[] trees; + /** Position of the current node in the digits. */ + private int deepPos; + /** Stack pointer for the deep trees. */ + private int tTop = -1; + + /** Node stack. */ + private InnerNode[] nodes; + /** Position stack. */ + private int[] poss; + /** Stack pointer. */ + private int nTop = -1; + + /** Current leaf node. */ + private Node leaf; + /** Position inside the current leaf. */ + private int leafPos; + + /** + * Constructor for iterating over a single node. + * @param root root node + * @param start starting position + */ + @SuppressWarnings("unchecked") + FingerTreeIterator(final Node root, final long start) { + this.n = root.size(); + this.index = start; + + if(root instanceof InnerNode) { + this.nodes = new InnerNode[8]; + this.poss = new int[8]; + this.nTop = 0; + this.nodes[0] = (InnerNode) root; + } else { + this.leaf = (Node) root; + this.leafPos = (int) start; + } + + assert start >= 0 && start <= this.n; + } + + /** + * Constructor for iterating over a deep fingertree. + * @param tree finger tree + * @param start start position + */ + @SuppressWarnings("unchecked") + FingerTreeIterator(final DeepTree tree, final long start) { + this.n = tree.size(); + this.index = start; + + this.trees = new DeepTree[8]; + this.trees[0] = tree; + this.tTop = 0; + + this.nodes = new InnerNode[8]; + this.poss = new int[8]; + this.nTop = -1; + } + + /** + * Returns a list iterator for the given finger tree starting at the given position. + * @param element type + * @param tree finger tree + * @param start starting position + * @return the iterator + */ + static ListIterator get(final FingerTree tree, final long start) { + if(tree.isEmpty()) return Collections.emptyListIterator(); + if(tree instanceof SingletonTree) return new FingerTreeIterator<>(tree.head(), start); + return new FingerTreeIterator<>((DeepTree) tree, start); + } + + /** + * Initializes this iterator by descending to the correct leaf node. + */ + @SuppressWarnings("unchecked") + private void init() { + Node node; + long pos = Math.min(index, n - 1); + + if(tTop >= 0) { + for(;;) { + final DeepTree curr = trees[tTop]; + if(pos < curr.leftSize) { + // left digit + final Node[] left = curr.left; + int i = 0; + for(;; i++) { + node = left[i]; + final long sz = node.size(); + if(pos < sz) break; + pos -= sz; + } + deepPos = i - left.length; + break; + } + pos -= curr.leftSize; + + final FingerTree mid = curr.middle; + final long midSize = mid.size(); + if(pos >= midSize) { + // right digit + pos -= midSize; + final Node[] right = curr.right; + int i = 0; + for(;; i++) { + node = right[i]; + final long sz = node.size(); + if(pos < sz) break; + pos -= sz; + } + deepPos = i + 1; + break; + } + + + if(mid instanceof SingletonTree) { + // single middle node + node = mid.head(); + deepPos = 0; + break; + } + + // go one level deeper + if(++tTop == trees.length) trees = Arrays.copyOf(trees, 2 * tTop); + trees[tTop] = (DeepTree) mid; + } + } else { + deepPos = 0; + node = nodes[0]; + nTop = -1; + } + + Node curr = node; + while(curr instanceof InnerNode) { + final InnerNode inner = (InnerNode) curr; + + int idx = 0; + Node sub = inner.getSub(0); + for(;;) { + final long sz = sub.size(); + if(pos < sz) break; + pos -= sz; + sub = inner.getSub(++idx); + } + + if(++nTop == nodes.length) { + nodes = Arrays.copyOf(nodes, 2 * nTop); + poss = Arrays.copyOf(poss, 2 * nTop); + } + nodes[nTop] = inner; + poss[nTop] = idx; + curr = sub; + } + + leaf = (Node) curr; + leafPos = (int) (index < n ? pos : pos + 1); + } + + @Override + public int nextIndex() { + return (int) index; + } + + @Override + public boolean hasNext() { + return index < n; + } + + @Override + @SuppressWarnings("unchecked") + public E next() { + if(index >= n) throw new NoSuchElementException(); + if(leaf == null) init(); + + index++; + if(leafPos < leaf.arity()) return leaf.getSub(leafPos++); + + // leaf drained, backtrack + while(nTop >= 0 && poss[nTop] == nodes[nTop].arity() - 1) nTop--; + + final Node start; + if(nTop >= 0) { + // go to next sub-node + start = nodes[nTop].getSub(++poss[nTop]); + } else { + // node drained, move to the next one + final DeepTree curr = trees[tTop]; + if(deepPos < -1) { + // go to next node in digit + ++deepPos; + start = curr.left[curr.left.length + deepPos]; + } else if(deepPos == -1) { + // left digit drained + final FingerTree mid = curr.middle; + if(mid instanceof EmptyTree) { + // skip empty middle tree + deepPos = 1; + start = curr.right[0]; + } else if(mid instanceof SingletonTree) { + // iterate through the one middle node + deepPos = 0; + start = ((SingletonTree) mid).elem; + } else { + final DeepTree deep = (DeepTree) mid; + if(++tTop == trees.length) { + final DeepTree[] newTrees = new DeepTree[2 * tTop]; + System.arraycopy(trees, 0, newTrees, 0, tTop); + trees = newTrees; + } + trees[tTop] = deep; + deepPos = -deep.left.length; + start = deep.left[0]; + } + } else if(deepPos == 0) { + // we are in a single middle node + deepPos = 1; + start = curr.right[0]; + } else { + // we are in the right digit + final int p = deepPos - 1; + if(p < curr.right.length - 1) { + // go to next node in digit + deepPos++; + start = curr.right[p + 1]; + } else { + // backtrack one level + trees[tTop] = null; + deepPos = 0; + tTop--; + start = trees[tTop].right[0]; + deepPos = 1; + } + } + } + + Node sub = start; + while(sub instanceof InnerNode) { + if(++nTop == nodes.length) { + final InnerNode[] newNodes = new InnerNode[2 * nTop]; + System.arraycopy(nodes, 0, newNodes, 0, nTop); + nodes = newNodes; + poss = Arrays.copyOf(poss, 2 * nTop); + } + nodes[nTop] = (InnerNode) sub; + poss[nTop] = 0; + sub = nodes[nTop].getSub(0); + } + + leaf = (Node) sub; + leafPos = 1; + return leaf.getSub(0); + } + + @Override + public int previousIndex() { + return (int) (index - 1); + } + + @Override + public boolean hasPrevious() { + return index > 0; + } + + @Override + @SuppressWarnings("unchecked") + public E previous() { + if(index <= 0) throw new NoSuchElementException(); + if(leaf == null) init(); + + --index; + if(leafPos > 0) return leaf.getSub(--leafPos); + + // leaf drained, backtrack + while(nTop >= 0 && poss[nTop] == 0) nTop--; + + final Node start; + if(nTop >= 0) { + // go to previous sub-node + start = nodes[nTop].getSub(--poss[nTop]); + } else { + // node drained, move to the previous one + final DeepTree curr = trees[tTop]; + if(deepPos > 1) { + // go to next node in right digit + --deepPos; + start = curr.right[deepPos - 1]; + } else if(deepPos == 1) { + // right digit drained + final FingerTree mid = curr.middle; + if(mid instanceof EmptyTree) { + // skip empty middle tree + final int l = curr.left.length; + start = curr.left[l - 1]; + deepPos = -1; + } else if(mid instanceof SingletonTree) { + // iterate through the one middle node + start = ((SingletonTree) mid).elem; + deepPos = 0; + } else { + // go into the middle tree + final DeepTree deep = (DeepTree) mid; + if(++tTop == trees.length) { + final DeepTree[] newTrees = new DeepTree[2 * tTop]; + System.arraycopy(trees, 0, newTrees, 0, tTop); + trees = newTrees; + } + trees[tTop] = deep; + final int r = deep.right.length; + start = deep.right[r - 1]; + deepPos = r; + } + } else if(deepPos == 0) { + start = curr.left[curr.left.length - 1]; + deepPos = -1; + } else { + // we are in the left digit + final int l = curr.left.length, p = l + deepPos; + if(p > 0) { + // go to previous node in digit + --deepPos; + start = curr.left[p - 1]; + } else { + // backtrack one level + trees[tTop] = null; + --tTop; + final Node[] left = trees[tTop].left; + start = left[left.length - 1]; + deepPos = -1; + } + } + } + + Node sub = start; + while(sub instanceof InnerNode) { + if(++nTop == nodes.length) { + final InnerNode[] newNodes = new InnerNode[2 * nTop]; + System.arraycopy(nodes, 0, newNodes, 0, nTop); + nodes = newNodes; + poss = Arrays.copyOf(poss, 2 * nTop); + } + nodes[nTop] = (InnerNode) sub; + poss[nTop] = sub.arity() - 1; + sub = nodes[nTop].getSub(poss[nTop]); + } + + leaf = (Node) sub; + leafPos = sub.arity() - 1; + return leaf.getSub(leafPos); + } + + @Override + public void set(final E e) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(final E e) { + throw new UnsupportedOperationException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/FingerTree.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/FingerTree.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/FingerTree.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/FingerTree.java 2015-07-14 10:54:40.000000000 +0000 @@ -12,6 +12,11 @@ * @param element type */ public abstract class FingerTree implements Iterable { + /** Maximum number of children in an inner node, tested values are 3 and 4. */ + static final int MAX_ARITY = 4; + /** Maximum length of a digit. */ + static final int MAX_DIGIT = MAX_ARITY + 1; + /** * Returns the empty finger tree. * @param element type @@ -19,7 +24,7 @@ */ @SuppressWarnings("unchecked") public static final FingerTree empty() { - return (FingerTree) Empty.INSTANCE; + return (FingerTree) EmptyTree.INSTANCE; } /** @@ -29,7 +34,7 @@ * @return the singleton finger tree */ public static final FingerTree singleton(final Node leaf) { - return new Single<>(leaf); + return new SingletonTree<>(leaf); } /** @@ -37,7 +42,7 @@ * @return {@code true} if the node is empty, {@code false} otherwise */ public final boolean isEmpty() { - return this == Empty.INSTANCE; + return this == EmptyTree.INSTANCE; } /** @@ -52,13 +57,13 @@ int level = 0; final Node digit; while(true) { - if(curr instanceof Single) { + if(curr instanceof SingletonTree) { // we unpack the contained node one level - digit = ((Single) curr).elem; + digit = ((SingletonTree) curr).elem; break; } - final Deep deep = (Deep) curr; + final DeepTree deep = (DeepTree) curr; // check if index is in left digit if(pos < deep.leftSize) { Node nd = null; @@ -96,26 +101,12 @@ Node nd = digit; for(; level > 0; level--) { - if(nd instanceof InnerNode2) { - final InnerNode2 deep = (InnerNode2) nd; - if(pos < deep.l) { - nd = deep.child0; - } else { - nd = deep.child1; - pos -= deep.l; - } - } else { - final InnerNode3 deep = (InnerNode3) nd; - if(pos < deep.l) { - nd = deep.child0; - } else if(pos < deep.m) { - nd = deep.child1; - pos -= deep.l; - } else { - nd = deep.child2; - pos -= deep.m; - } - } + final InnerNode deep = (InnerNode) nd; + final long[] bounds = deep.bounds; + int p = 0; + while(pos >= bounds[p]) p++; + if(p > 0) pos -= bounds[p - 1]; + nd = deep.children[p]; } @SuppressWarnings("unchecked") @@ -172,10 +163,12 @@ /** * Concatenates this finger tree with the given one. * @param mid nodes between the two trees + * @param sz sum of the sizes of all nodes in the middle array * @param other the other tree * @return concatenation of both trees */ - public abstract FingerTree concat(final Node[] mid, final FingerTree other); + public abstract FingerTree concat(final Node[] mid, final long sz, + final FingerTree other); /** * Creates a reversed version of this tree. @@ -231,50 +224,51 @@ * @param size size of all nodes combined * @return constructed tree */ - public static FingerTree buildTree(final Node[] nodes, final int n, + static FingerTree buildTree(final Node[] nodes, final int n, final long size) { - if(n == 1) return new Single<>(nodes[0]); - if(n < 7) { + + if(n == 0) return EmptyTree.getInstance(); + if(n == 1) return new SingletonTree<>(nodes[0]); + if(n <= 2 * MAX_ARITY) { final int mid = n / 2; @SuppressWarnings("unchecked") final Node[] left = new Node[mid], right = new Node[n - mid]; System.arraycopy(nodes, 0, left, 0, mid); System.arraycopy(nodes, mid, right, 0, n - mid); - return Deep.get(left, right, size); + return DeepTree.get(left, right, size); } - final int k = n == 7 ? 2 : 3; + final int k = Math.min((n - MAX_ARITY) / 2, MAX_ARITY); @SuppressWarnings("unchecked") final Node[] left = new Node[k], right = new Node[k]; System.arraycopy(nodes, 0, left, 0, k); System.arraycopy(nodes, n - k, right, 0, k); - final long leftSize = Deep.size(left), rightSize = Deep.size(right); + final long leftSize = DeepTree.size(left), rightSize = DeepTree.size(right); - int remaining = n - 2 * k; - int i = k, j = 0; @SuppressWarnings("unchecked") final Node, E>[] outNodes = (Node, E>[]) nodes; - while(remaining > 4 || remaining == 3) { - outNodes[j++] = new InnerNode3<>(nodes[i++], nodes[i++], nodes[i++]); - remaining -= 3; - } - - while(remaining > 0) { - outNodes[j++] = new InnerNode2<>(nodes[i++], nodes[i++]); - remaining -= 2; + final int remaining = n - 2 * k, ns = (remaining + MAX_ARITY - 1) / MAX_ARITY; + for(int i = 0, j = 0; i < ns; i++) { + final int rem = ns - i, sz = (remaining - j + rem - 1) / rem; + @SuppressWarnings("unchecked") + final Node[] ch = new Node[sz]; + System.arraycopy(nodes, j, ch, 0, sz); + outNodes[i] = new InnerNode<>(ch); + j += sz; } - final FingerTree, E> middle = buildTree(outNodes, j, size - leftSize - rightSize); - return new Deep<>(left, leftSize, middle, right, size); + final FingerTree, E> middle = buildTree(outNodes, ns, size - leftSize - rightSize); + return new DeepTree<>(left, leftSize, middle, right, size); } /** * Adds all nodes in the given array to the given side of this tree. * @param nodes the nodes + * @param sz sum of the sizes of all nodes in the array * @param left insertion direction, {@code true} adds to the left, {@code false} to the right * @return resulting tree */ - abstract FingerTree addAll(final Node[] nodes, final boolean left); + abstract FingerTree addAll(final Node[] nodes, final long sz, final boolean left); @Override public final String toString() { @@ -298,21 +292,39 @@ public abstract long checkInvariants(); /** - * Returns an array containing the number of elements stored at each level of the tree. - * @param depth current depth - * @return array of sizes - */ - public abstract long[] sizes(int depth); - - /** * Creates a {@link ListIterator} over the elements in this tree. - * @param reverse flag for starting at the back of the tree + * @param start starting position + * (i.e. the position initially returned by {@link ListIterator#nextIndex()}) * @return the list iterator */ - public abstract ListIterator listIterator(final boolean reverse); + public final ListIterator listIterator(final long start) { + return FingerTreeIterator.get(this, start); + } @Override public final ListIterator iterator() { - return listIterator(false); + return listIterator(0); + } + + /** + * Writes a string representation of the given object to the given strun builder. + * @param obj object to write + * @param sb string builder + * @param indent indentation level + */ + static void toString(final Object obj, final StringBuilder sb, final int indent) { + if(obj instanceof InnerNode) { + ((InnerNode) obj).toString(sb, indent); + } else if(obj instanceof PartialInnerNode) { + ((PartialInnerNode) obj).toString(sb, indent); + } else { + boolean fst = true; + for(final String line : obj.toString().split("\r\n?|\n")) { + for(int i = 0; i < indent; i++) sb.append(' ').append(' '); + sb.append(line); + if(fst) fst = false; + else sb.append('\n'); + } + } } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/InnerNode2.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/InnerNode2.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/InnerNode2.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/InnerNode2.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,266 +0,0 @@ -package org.basex.query.util.fingertree; - -/** - * An inner node with two sub-nodes. - * - * @author BaseX Team 2005-15, BSD License - * @author Leo Woerteler - * - * @param node type - * @param element type - */ -final class InnerNode2 extends InnerNode { - /** First sub-node. */ - final Node child0; - /** Second sub-node. */ - final Node child1; - /** End of the first sub-node. */ - final long l; - /** End of the second sub-node. */ - final long r; - - /** - * Constructor for a binary node. - * @param a first sub-node - * @param b second sub-node - */ - InnerNode2(final Node a, final Node b) { - child0 = a; - l = a.size(); - child1 = b; - r = l + b.size(); - } - - @Override - protected long size() { - return r; - } - - @Override - protected InnerNode reverse() { - return new InnerNode2<>(child1.reverse(), child0.reverse()); - } - - @Override - protected boolean insert(final Node, E>[] siblings, final long pos, final E val) { - final InnerNode left = (InnerNode) siblings[0], - right = (InnerNode) siblings[2]; - @SuppressWarnings("unchecked") - final Node[] sub = (Node[]) siblings; - final Node, E> inserted; - if(pos < l || pos == l && l <= r - l) { - // insert into left sub-tree - sub[0] = null; - sub[2] = child1; - child0.insert(sub, pos, val); - final Node a = sub[1], b = sub[2], c = sub[3]; - inserted = c == null ? new InnerNode2<>(a, b) : new InnerNode3<>(a, b, c); - } else { - // insert into right sub-tree - sub[0] = child0; - sub[2] = null; - child1.insert(sub, pos - l, val); - final Node a = sub[0], b = sub[1], c = sub[2]; - inserted = c == null ? new InnerNode2<>(a, b) : new InnerNode3<>(a, b, c); - } - - // restore the array - siblings[0] = left; - siblings[1] = inserted; - siblings[2] = right; - siblings[3] = null; - return false; - } - - @Override - protected Node, E>[] remove(final Node, E> left, - final Node, E> right, final long pos) { - final Node[] res; - final Node split0, split1; - if(pos < l) { - res = child0.remove(null, child1, pos); - if(res[1] != null) { - split0 = res[1]; - split1 = res[2]; - } else { - split0 = res[2]; - split1 = null; - } - } else { - res = child1.remove(child0, null, pos - l); - split0 = res[0]; - split1 = res[1]; - } - - @SuppressWarnings("unchecked") - final Node, E>[] out = (Node, E>[]) res; - - if(split1 != null) { - // nodes were not merged - out[0] = left; - out[1] = new InnerNode2<>(split0, split1); - out[2] = right; - return out; - } - - if(left != null && left instanceof InnerNode3) { - // steal from the left - final InnerNode3 steal = (InnerNode3) left; - out[0] = new InnerNode2<>(steal.child0, steal.child1); - out[1] = new InnerNode2<>(steal.child2, split0); - out[2] = right; - return out; - } - - if(right != null && right instanceof InnerNode3) { - // steal from the right - final InnerNode3 steal = (InnerNode3) right; - out[0] = left; - out[1] = new InnerNode2<>(split0, steal.child0); - out[2] = new InnerNode2<>(steal.child1, steal.child2); - return out; - } - - if(left != null && (right == null || left.size() < right.size())) { - // merge with left neighbor - final InnerNode2 merge = (InnerNode2) left; - out[0] = new InnerNode3<>(merge.child0, merge.child1, split0); - out[1] = null; - out[2] = right; - } else { - // merge with right neighbor - final InnerNode2 merge = (InnerNode2) right; - out[0] = left; - out[1] = null; - out[2] = new InnerNode3<>(split0, merge.child0, merge.child1); - } - - return out; - } - - @Override - protected NodeLike, E> remove(final long pos) { - if(pos < l) { - final Node[] res = child0.remove(null, child1, pos); - if(res[1] == null) return new PartialInnerNode<>(res[2]); - return new InnerNode2<>(res[1], res[2]); - } - - final Node[] res = child1.remove(child0, null, pos - l); - if(res[1] == null) return new PartialInnerNode<>(res[0]); - return new InnerNode2<>(res[0], res[1]); - } - - @Override - protected NodeLike, E> slice(final long start, final long len) { - if(start >= l) { - // everything is in right sub-node - return new PartialInnerNode<>(len == r - l ? child1 : child1.slice(start - l, len)); - } - - if(start + len <= l) { - // everything is in left sub-node - return new PartialInnerNode<>(len == l ? child0 : child0.slice(start, len)); - } - - // both nodes are involved - final long inL = l - start, inR = len - inL; - final NodeLike left = inL == l ? child0 : child0.slice(start, inL); - final NodeLike right = inR == r - l ? child1 : child1.slice(0, inR); - if(left instanceof Node && right instanceof Node) - return new InnerNode2<>((Node) left, (Node) right); - - final NodeLike[] res = left.concat(right); - return res[1] == null ? new PartialInnerNode<>(res[0]) : - new InnerNode2<>((Node) res[0], (Node) res[1]); - } - - @Override - protected NodeLike, E>[] concat(final NodeLike, E> other) { - if(other instanceof Node) { - @SuppressWarnings("unchecked") - final NodeLike, E>[] out = new NodeLike[] { this, other }; - return out; - } - - final NodeLike sub = ((PartialInnerNode) other).sub; - final NodeLike[] merged = child1.concat(sub); - final Node a = (Node) merged[0], b = (Node) merged[1]; - @SuppressWarnings("unchecked") - final NodeLike, E>[] out = (NodeLike, E>[]) merged; - if(b == null) { - out[0] = new InnerNode2<>(child0, a); - } else { - out[0] = new InnerNode3<>(child0, a, b); - out[1] = null; - } - return out; - } - - @Override - protected int append(final NodeLike, E>[] nodes, final int pos) { - if(pos == 0) { - nodes[0] = this; - return 1; - } - - final NodeLike, E> left = nodes[pos - 1]; - if(left instanceof Node) { - nodes[pos] = this; - return pos + 1; - } - - final NodeLike, E>[] joined = left.concat(this); - nodes[pos - 1] = joined[0]; - if(joined[1] == null) return pos; - nodes[pos] = joined[1]; - return pos + 1; - } - - @Override - protected void toString(final StringBuilder sb, final int indent) { - for(int i = 0; i < indent; i++) sb.append(" "); - sb.append("Node(").append(size()).append(")[\n"); - child0.toString(sb, indent + 1); - sb.append("\n"); - child1.toString(sb, indent + 1); - sb.append("\n"); - for(int i = 0; i < indent; i++) sb.append(" "); - sb.append("]"); - } - - @Override - protected long checkInvariants() { - final long lCheck = child0.checkInvariants(); - if(lCheck != l) throw new AssertionError("Wrong l: " + lCheck + " vs. " + l); - final long rCheck = lCheck + child1.checkInvariants(); - if(rCheck != r) throw new AssertionError("Wrong r: " + rCheck + " vs. " + r); - return rCheck; - } - - @Override - protected int arity() { - return 2; - } - - @Override - protected Node getSub(final int pos) { - return pos == 0 ? child0 : child1; - } - - @Override - protected InnerNode replaceFirst(final Node newFirst) { - return newFirst == child0 ? this : new InnerNode2<>(newFirst, child1); - } - - @Override - protected InnerNode replaceLast(final Node newLast) { - return newLast == child1 ? this : new InnerNode2<>(child0, newLast); - } - - @Override - @SuppressWarnings("unchecked") - Node[] copyChildren() { - return new Node[] { child0, child1 }; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/InnerNode3.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/InnerNode3.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/InnerNode3.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/InnerNode3.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,315 +0,0 @@ -package org.basex.query.util.fingertree; - -/** - * An inner node with three sub-nodes. - * - * @author BaseX Team 2005-15, BSD License - * @author Leo Woerteler - * - * @param node type - * @param element type - */ -final class InnerNode3 extends InnerNode { - /** First sub-node. */ - final Node child0; - /** Second sub-node. */ - final Node child1; - /** Third sub-node. */ - final Node child2; - /** End of the first sub-node. */ - final long l; - /** End of the second sub-node. */ - final long m; - /** End of the third sub-node. */ - final long r; - - /** - * Constructor for a trinary node. - * @param a first sub-node - * @param b second sub-node - * @param c third sub-node - */ - InnerNode3(final Node a, final Node b, final Node c) { - this.child0 = a; - this.l = a.size(); - this.child1 = b; - this.m = l + b.size(); - this.child2 = c; - this.r = m + c.size(); - } - - @Override - protected long size() { - return r; - } - - @Override - protected InnerNode reverse() { - return new InnerNode3<>(child2.reverse(), child1.reverse(), child0.reverse()); - } - - @Override - protected boolean insert(final Node, E>[] siblings, final long pos, final E val) { - final InnerNode left = (InnerNode) siblings[0], - right = (InnerNode) siblings[2]; - @SuppressWarnings("unchecked") - final Node[] sub = (Node[]) siblings; - final Node a, b, c, d; - if(pos <= l) { - // insert left - sub[0] = null; - sub[2] = child1; - child0.insert(sub, pos, val); - a = sub[1]; - b = sub[2]; - c = sub[3] == null ? child2 : sub[3]; - d = sub[3] == null ? null : child2; - } else if(pos <= m) { - // insert in the middle - sub[0] = child0; - sub[2] = child2; - child1.insert(sub, pos - l, val); - a = sub[0]; - b = sub[1]; - c = sub[2]; - d = sub[3]; - } else { - // insert right - sub[0] = child1; - sub[2] = null; - child2.insert(sub, pos - m, val); - a = child0; - b = sub[0]; - c = sub[1]; - d = sub[2]; - } - - if(d == null) { - // no split - siblings[0] = left; - siblings[1] = new InnerNode3<>(a, b, c); - siblings[2] = right; - siblings[3] = null; - return false; - } - - if(left != null && left instanceof InnerNode2) { - // merge with left sibling - final InnerNode2 deep2 = (InnerNode2) left; - siblings[0] = new InnerNode3<>(deep2.child0, deep2.child1, a); - siblings[1] = new InnerNode3<>(b, c, d); - siblings[2] = right; - siblings[3] = null; - return false; - } - - if(right != null && right instanceof InnerNode2) { - // merge with left sibling - final InnerNode2 deep2 = (InnerNode2) right; - siblings[0] = left; - siblings[1] = new InnerNode3<>(a, b, c); - siblings[2] = new InnerNode3<>(d, deep2.child0, deep2.child1); - siblings[3] = null; - return false; - } - - // split the node - siblings[0] = left; - siblings[1] = new InnerNode2<>(a, b); - siblings[2] = new InnerNode2<>(c, d); - siblings[3] = right; - return true; - } - - @Override - protected InnerNode remove(final long pos) { - if(pos < l) { - final Node[] res = child0.remove(null, child1, pos); - if(res[1] == null) return new InnerNode2<>(res[2], child2); - return new InnerNode3<>(res[1], res[2], child2); - } else if(pos < m) { - final Node[] res = child1.remove(child0, child2, pos - l); - if(res[1] == null) return new InnerNode2<>(res[0], res[2]); - return new InnerNode3<>(res[0], res[1], res[2]); - } else { - final Node[] res = child2.remove(child1, null, pos - m); - if(res[1] == null) return new InnerNode2<>(child0, res[0]); - return new InnerNode3<>(child0, res[0], res[1]); - } - } - - @Override - protected Node, E>[] remove(final Node, E> left, - final Node, E> right, final long pos) { - final Node[] res; - final Node, E> node; - if(pos < l) { - res = child0.remove(null, child1, pos); - node = res[1] == null ? new InnerNode2<>(res[2], child2) - : new InnerNode3<>(res[1], res[2], child2); - } else if(pos < m) { - res = child1.remove(child0, child2, pos - l); - node = res[1] == null ? new InnerNode2<>(res[0], res[2]) - : new InnerNode3<>(res[0], res[1], res[2]); - } else { - res = child2.remove(child1, null, pos - m); - node = res[1] == null ? new InnerNode2<>(child0, res[0]) - : new InnerNode3<>(child0, res[0], res[1]); - } - - @SuppressWarnings("unchecked") - final Node, E>[] out = (Node, E>[]) res; - out[0] = left; - out[1] = node; - out[2] = right; - return out; - } - - @Override - protected NodeLike, E> slice(final long start, final long len) { - final long end = start + len; - if(start < l) { - // left node involved - final long in0 = Math.min(l - start, len); - final NodeLike slice0 = in0 == l ? child0 : child0.slice(start, in0); - if(end <= m) { - // no right node - if(end <= l) return new PartialInnerNode<>(slice0); - final NodeLike slice1 = end == m ? child1 : child1.slice(0, end - l); - if(slice0 instanceof Node && slice1 instanceof Node) - return new InnerNode2<>((Node) slice0, (Node) slice1); - - // partial nodes have to be merged - final NodeLike[] merged = slice0.concat(slice1); - return merged[1] == null ? new PartialInnerNode<>(merged[0]) : - new InnerNode2<>((Node) merged[0], (Node) merged[1]); - } - - // middle is contained completely - final long in2 = end - m; - final NodeLike slice2 = in2 == r - m ? child2 : child2.slice(0, in2); - @SuppressWarnings("unchecked") - final NodeLike[] ns = new NodeLike[3]; - final int p0 = slice0.append(ns, 0), p1 = child1.append(ns, p0), p2 = slice2.append(ns, p1); - return p2 == 1 ? new PartialInnerNode<>(ns[0]) - : p2 == 2 ? new InnerNode2<>((Node) ns[0], (Node) ns[1]) - : new InnerNode3<>((Node) ns[0], (Node) ns[1], (Node) ns[2]); - } - - if(start < m) { - // middle node included - final long in1 = Math.min(m - start, len); - final NodeLike slice1 = in1 == m - l ? child1 : child1.slice(start - l, in1); - if(end <= m) return new PartialInnerNode<>(slice1); - - // middle and last - final long in2 = end - m; - final NodeLike slice2 = in2 == r - m ? child2 : child2.slice(0, in2); - if(slice1 instanceof Node && slice2 instanceof Node) - return new InnerNode2<>((Node) slice1, (Node) slice2); - - // partial nodes have to be merged - final NodeLike[] merged = slice1.concat(slice2); - return merged[1] == null ? new PartialInnerNode<>(merged[0]) : - new InnerNode2<>((Node) merged[0], (Node) merged[1]); - } - - // only last node - final NodeLike slice2 = r - m == len ? child2 : child2.slice(start - m, len); - return new PartialInnerNode<>(slice2); - } - - @Override - protected NodeLike, E>[] concat(final NodeLike, E> other) { - if(other instanceof Node) { - @SuppressWarnings("unchecked") - final NodeLike, E>[] out = new NodeLike[] { this, other }; - return out; - } - - final NodeLike sub = ((PartialInnerNode) other).sub; - final NodeLike[] merged = child2.concat(sub); - final Node a = (Node) merged[0], b = (Node) merged[1]; - @SuppressWarnings("unchecked") - final NodeLike, E>[] out = (NodeLike, E>[]) merged; - if(b == null) { - out[0] = new InnerNode3<>(child0, child1, a); - } else { - out[0] = new InnerNode2<>(child0, child1); - out[1] = new InnerNode2<>(a, b); - } - - return out; - } - - @Override - protected int append(final NodeLike, E>[] nodes, final int pos) { - if(pos == 0) { - nodes[0] = this; - return 1; - } - - final NodeLike, E> left = nodes[pos - 1]; - if(left instanceof Node) { - nodes[pos] = this; - return pos + 1; - } - - final NodeLike, E>[] joined = left.concat(this); - nodes[pos - 1] = joined[0]; - if(joined[1] == null) return pos; - nodes[pos] = joined[1]; - return pos + 1; - } - - @Override - protected void toString(final StringBuilder sb, final int indent) { - for(int i = 0; i < indent; i++) sb.append(" "); - sb.append("Node(").append(size()).append(")[\n"); - child0.toString(sb, indent + 1); - sb.append("\n"); - child1.toString(sb, indent + 1); - sb.append("\n"); - child2.toString(sb, indent + 1); - sb.append("\n"); - for(int i = 0; i < indent; i++) sb.append(" "); - sb.append("]"); - } - - @Override - protected long checkInvariants() { - final long lCheck = child0.checkInvariants(); - if(lCheck != l) throw new AssertionError("Wrong l: " + lCheck + " vs. " + l); - final long mCheck = l + child1.checkInvariants(); - if(mCheck != m) throw new AssertionError("Wrong m: " + mCheck + " vs. " + m); - final long rCheck = m + child2.checkInvariants(); - if(rCheck != r) throw new AssertionError("Wrong r: " + rCheck + " vs. " + r); - return r; - } - - @Override - protected int arity() { - return 3; - } - - @Override - protected Node getSub(final int pos) { - return pos == 0 ? child0 : pos == 1 ? child1 : child2; - } - - @Override - protected InnerNode replaceFirst(final Node newFirst) { - return newFirst == child0 ? this : new InnerNode3<>(newFirst, child1, child2); - } - - @Override - protected InnerNode replaceLast(final Node newLast) { - return newLast == child2 ? this : new InnerNode3<>(child0, child1, newLast); - } - - @Override - @SuppressWarnings("unchecked") - Node[] copyChildren() { - return new Node[] { child0, child1, child2 }; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/InnerNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/InnerNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/InnerNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/InnerNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,10 +9,435 @@ * @param node type * @param element type */ -abstract class InnerNode extends Node, E> { +final class InnerNode implements Node, E> { + /** Child nodes. */ + final Node[] children; + /** Right bound for the elements' index in each sub-node. */ + final long[] bounds; + /** - * Returns a copy of this node's sub-nodes. - * @return an array containing this node's children + * Constructor. + * @param children children array */ - abstract Node[] copyChildren(); + InnerNode(final Node[] children) { + final int n = children.length; + this.children = children; + this.bounds = new long[n]; + long off = 0; + for(int i = 0; i < n; i++) { + off += children[i].size(); + bounds[i] = off; + } + assert 2 <= n && n <= FingerTree.MAX_ARITY; + } + + @Override + public long size() { + return bounds[bounds.length - 1]; + } + + @Override + public int arity() { + return bounds.length; + } + + @Override + public Node getSub(final int pos) { + return children[pos]; + } + + @Override + public Node, E> reverse() { + final int n = children.length; + @SuppressWarnings("unchecked") + final Node[] newChildren = new Node[n]; + for(int i = 0; i < n; i++) newChildren[i] = children[n - 1 - i].reverse(); + return new InnerNode<>(newChildren); + } + + @Override + public boolean insert(final Node, E>[] siblings, final long index, final E val) { + final Node, E> left = siblings[0], right = siblings[2]; + + int i = 0; + final int n = bounds.length; + while(index > bounds[i]) i++; + final long off = i == 0 ? index : index - bounds[i - 1]; + + @SuppressWarnings("unchecked") + final Node[] subs = (Node[]) siblings; + subs[0] = i == 0 ? null : children[i - 1]; + subs[2] = i == n - 1 ? null : children[i + 1]; + + final int l = Math.max(0, i - 1), r = Math.min(i + 1, n - 1); + if(!children[i].insert(subs, off, val)) { + // no split + final Node[] out = children.clone(); + System.arraycopy(subs, i == 0 ? 1 : 0, out, l, r - l + 1); + siblings[0] = left; + siblings[1] = new InnerNode<>(out); + siblings[2] = right; + return false; + } + + @SuppressWarnings("unchecked") + final Node[] temp = new Node[n + 1]; + if(i == 0) { + System.arraycopy(subs, 1, temp, 0, 3); + System.arraycopy(children, 2, temp, 3, n - 2); + } else if(i < n - 1) { + System.arraycopy(children, 0, temp, 0, l); + System.arraycopy(subs, 0, temp, l, 4); + System.arraycopy(children, r + 1, temp, l + 4, n - l - 3); + } else { + System.arraycopy(children, 0, temp, 0, n - 2); + System.arraycopy(subs, 0, temp, n - 2, 3); + } + + if(n < FingerTree.MAX_ARITY) { + // still small enough + siblings[0] = left; + siblings[1] = new InnerNode<>(temp); + siblings[2] = right; + return false; + } + + if(left != null) { + final int la = left.arity(), move = (FingerTree.MAX_ARITY - la + 1) / 2; + if(move > 0) { + // left node has capacity + final Node[] ch = ((InnerNode) left).children; + @SuppressWarnings("unchecked") + final Node[] ls = new Node[la + move], rs = new Node[n + 1 - move]; + System.arraycopy(ch, 0, ls, 0, la); + System.arraycopy(temp, 0, ls, la, move); + System.arraycopy(temp, move, rs, 0, rs.length); + siblings[0] = new InnerNode<>(ls); + siblings[1] = new InnerNode<>(rs); + siblings[2] = right; + return false; + } + } + + if(right != null) { + final int ra = right.arity(), move = (FingerTree.MAX_ARITY - ra + 1) / 2; + if(move > 0) { + // right node has capacity + final Node[] ch = ((InnerNode) right).children; + @SuppressWarnings("unchecked") + final Node[] ls = new Node[n + 1 - move], rs = new Node[ra + move]; + System.arraycopy(temp, 0, ls, 0, ls.length); + System.arraycopy(temp, ls.length, rs, 0, move); + System.arraycopy(ch, 0, rs, move, ra); + siblings[0] = left; + siblings[1] = new InnerNode<>(ls); + siblings[2] = new InnerNode<>(rs); + return false; + } + } + + if(left != null) { + // merge with left neighbor + final Node[] ch = ((InnerNode) left).children; + final int la = ch.length, k = la + n + 1, ml = k / 3, ll = k - 2 * ml, inL = la - ll; + @SuppressWarnings("unchecked") + final Node[] ls = new Node[ll], mid1 = new Node[ml], mid2 = new Node[ml]; + System.arraycopy(ch, 0, ls, 0, ll); + System.arraycopy(ch, ll, mid1, 0, inL); + System.arraycopy(temp, 0, mid1, inL, ml - inL); + System.arraycopy(temp, ml - inL, mid2, 0, ml); + siblings[0] = inL == 0 ? left : new InnerNode<>(ls); + siblings[1] = new InnerNode<>(mid1); + siblings[2] = new InnerNode<>(mid2); + siblings[3] = right; + return true; + } + + if(right != null) { + // merge with right neighbor + final Node[] ch = ((InnerNode) right).children; + final int ra = ch.length, k = n + 1 + ra, ml = k / 3, rl = k - 2 * ml, inR = ra - rl; + @SuppressWarnings("unchecked") + final Node[] mid1 = new Node[ml], mid2 = new Node[ml], rs = new Node[rl]; + System.arraycopy(temp, 0, mid1, 0, ml); + System.arraycopy(temp, ml, mid2, 0, ml - inR); + System.arraycopy(ch, 0, mid2, ml - inR, inR); + System.arraycopy(ch, inR, rs, 0, rl); + siblings[0] = left; + siblings[1] = new InnerNode<>(mid1); + siblings[2] = new InnerNode<>(mid2); + siblings[3] = inR == 0 ? right : new InnerNode<>(rs); + return true; + } + + // split the node + final int ll = (n + 1) / 2, rl = n + 1 - ll; + @SuppressWarnings("unchecked") + final Node[] ls = new Node[ll], rs = new Node[rl]; + System.arraycopy(temp, 0, ls, 0, ll); + System.arraycopy(temp, ll, rs, 0, rl); + siblings[0] = null; + siblings[1] = new InnerNode<>(ls); + siblings[2] = new InnerNode<>(rs); + siblings[3] = null; + return true; + } + + @Override + public NodeLike, E>[] remove(final Node, E> left, + final Node, E> right, final long pos) { + int i = 0; + final int n = bounds.length; + while(pos >= bounds[i]) i++; + final long off = i == 0 ? pos : pos - bounds[i - 1]; + + final NodeLike[] res = children[i].remove( + i == 0 ? null : children[i - 1], + i == n - 1 ? null : children[i + 1], off); + + @SuppressWarnings("unchecked") + final NodeLike, E>[] out = (NodeLike, E>[]) res; + final Node l = (Node) res[0], m = (Node) res[1], r = (Node) res[2]; + + if(m != null) { + // no underflow + final Node[] ch = children.clone(); + if(i > 0) ch[i - 1] = l; + ch[i] = m; + if(i < n - 1) ch[i + 1] = r; + out[0] = left; + out[1] = new InnerNode<>(ch); + out[2] = right; + return out; + } + + if(n > 2) { + // still big enough + @SuppressWarnings("unchecked") + final Node[] ch = new Node[n - 1]; + if(i > 0) { + System.arraycopy(children, 0, ch, 0, i - 1); + ch[i - 1] = l; + } + if(i < n - 1) { + ch[i] = r; + System.arraycopy(children, i + 2, ch, i + 1, n - i - 2); + } + out[0] = left; + out[1] = new InnerNode<>(ch); + out[2] = right; + return out; + } + + // only one sub-node left + final Node single = i == 0 ? r : l; + + if(left != null && left.arity() > 2) { + // refill from left sibling + final Node[] ch = ((InnerNode) left).children; + final int a = ch.length, move = (a - 1) / 2; + @SuppressWarnings("unchecked") + final Node[] ls = new Node[a - move], ms = new Node[move + 1]; + System.arraycopy(ch, 0, ls, 0, a - move); + System.arraycopy(ch, a - move, ms, 0, move); + ms[move] = single; + out[0] = new InnerNode<>(ls); + out[1] = new InnerNode<>(ms); + out[2] = right; + return out; + } + + if(right != null && right.arity() > 2) { + // refill from right sibling + final Node[] ch = ((InnerNode) right).children; + final int a = ch.length, move = (a - 1) / 2; + @SuppressWarnings("unchecked") + final Node[] ms = new Node[move + 1], rs = new Node[a - move]; + ms[0] = single; + System.arraycopy(ch, 0, ms, 1, move); + System.arraycopy(ch, move, rs, 0, rs.length); + out[0] = left; + out[1] = new InnerNode<>(ms); + out[2] = new InnerNode<>(rs); + return out; + } + + if(left != null) { + // merge with left sibling + final Node[] ch = ((InnerNode) left).children; + final int a = ch.length; + @SuppressWarnings("unchecked") + final Node[] ls = new Node[a + 1]; + System.arraycopy(ch, 0, ls, 0, a); + ls[a] = single; + out[0] = new InnerNode<>(ls); + out[2] = right; + return out; + } + + if(right != null) { + // merge with right sibling + final Node[] ch = ((InnerNode) right).children; + final int a = ch.length; + @SuppressWarnings("unchecked") + final Node[] rs = new Node[a + 1]; + rs[0] = single; + System.arraycopy(ch, 0, rs, 1, a); + out[0] = left; + out[2] = new InnerNode<>(rs); + return out; + } + + // underflow + out[0] = null; + out[1] = new PartialInnerNode<>(single); + out[2] = null; + return out; + } + + @Override + @SuppressWarnings("unchecked") + public NodeLike, E> slice(final long start, final long len) { + // find the range of affected sub-nodes + int p = 0; + while(start >= bounds[p]) p++; + final long off = p == 0 ? start : start - bounds[p - 1], end = start + len - 1; + final int l = p; + while(end >= bounds[p]) p++; + final int r = p; + + // first node can be partial + final Node first = children[l]; + final long inFst = Math.min(bounds[l] - start, len); + final NodeLike fst = inFst == first.size() ? first : first.slice(off, inFst); + if(l == r) return new PartialInnerNode<>(fst); + + // more than one node affected + final NodeLike[] buffer = new NodeLike[r - l + 1]; + buffer[0] = fst; + int inBuffer = 1; + for(int i = l + 1; i < r; i++) inBuffer = children[i].append(buffer, inBuffer); + final Node last = children[r]; + final long inLst = start + len - bounds[r - 1]; + final NodeLike lst = inLst == last.size() ? last : last.slice(0, inLst); + inBuffer = lst.append(buffer, inBuffer); + + if(inBuffer == 1) { + // merged into a single sub-node + return new PartialInnerNode<>(buffer[0]); + } + + // enough children for a full node + final Node[] subs = new Node[inBuffer]; + System.arraycopy(buffer, 0, subs, 0, inBuffer); + return new InnerNode<>(subs); + } + + @Override + public long checkInvariants() { + final int a = children.length; + if(a < 2 || a > FingerTree.MAX_ARITY) throw new AssertionError("Wrong arity: " + a); + long b = 0; + for(int i = 0; i < a; i++) { + final Node ch = children[i]; + b += ch.checkInvariants(); + if(b != bounds[i]) throw new AssertionError("Wrong boundary: " + b); + } + return b; + } + + @Override + public int append(final NodeLike, E>[] nodes, final int pos) { + if(pos == 0 || nodes[pos - 1] instanceof InnerNode) { + nodes[pos] = this; + return pos + 1; + } + + final NodeLike sub = ((PartialInnerNode) nodes[pos - 1]).sub; + final int n = children.length; + final Node a, b; + if(sub instanceof Node) { + a = (Node) sub; + b = children[0]; + } else { + @SuppressWarnings("unchecked") + final NodeLike[] buffer = (NodeLike[]) nodes; + buffer[pos - 1] = sub; + if(children[0].append(buffer, pos) == pos) { + nodes[pos - 1] = replaceFirst((Node) buffer[pos - 1]); + return pos; + } + a = (Node) buffer[pos - 1]; + b = (Node) buffer[pos]; + } + + if(n < FingerTree.MAX_ARITY) { + @SuppressWarnings("unchecked") + final Node[] ch = new Node[n + 1]; + System.arraycopy(children, 1, ch, 2, n - 1); + ch[0] = a; + ch[1] = b; + nodes[pos - 1] = new InnerNode<>(ch); + nodes[pos] = null; + return pos; + } + + final int rl = (n + 1) / 2, ll = n + 1 - rl; + @SuppressWarnings("unchecked") + final Node[] ls = new Node[ll], rs = new Node[rl]; + System.arraycopy(children, 1, ls, 2, ll - 2); + ls[0] = a; + ls[1] = b; + System.arraycopy(children, ll - 1, rs, 0, rl); + nodes[pos - 1] = new InnerNode<>(ls); + nodes[pos] = new InnerNode<>(rs); + return pos + 1; + } + + + + /** + * Recursive helper method for {@link #toString()}. + * @param sb string builder + * @param indent indentation depth + */ + public void toString(final StringBuilder sb, final int indent) { + for(int i = 0; i < indent; i++) sb.append(" "); + sb.append("Node(").append(size()).append(")[\n"); + for(final Node sub : children) { + FingerTree.toString(sub, sb, indent + 1); + sb.append("\n"); + } + for(int i = 0; i < indent; i++) sb.append(" "); + sb.append("]"); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + toString(sb, 0); + return sb.toString(); + } + + /** + * Returns a version of this node where the first sub-node is the given one. + * @param newFirst new first sub-node + * @return resulting node + */ + Node, E> replaceFirst(final Node newFirst) { + final Node[] copy = children.clone(); + copy[0] = newFirst; + return new InnerNode<>(copy); + } + + /** + * Returns a version of this node where the last sub-node is the given one. + * @param newLast new last sub-node + * @return resulting node + */ + Node, E> replaceLast(final Node newLast) { + final Node[] copy = children.clone(); + copy[copy.length - 1] = newLast; + return new InnerNode<>(copy); + } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/NodeIterator.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/NodeIterator.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/NodeIterator.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/NodeIterator.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,138 +0,0 @@ -package org.basex.query.util.fingertree; - -import java.util.*; - -/** - * An {@link ListIterator} over the elements in a node. - * - * @author BaseX Team 2005-15, BSD License - * @author Leo Woerteler - * - * @param element type - */ -public final class NodeIterator implements ListIterator { - /** Node stack. */ - private InnerNode[] nodes; - /** Position stack. */ - private int[] poss; - /** Stack pointer. */ - private int top; - /** Current leaf node. */ - private Node leaf; - /** Position inside the current leaf. */ - private int leafPos; - - /** Current index. */ - private long index; - /** Number of elements in the root node. */ - private final long rootSize; - - /** - * Constructor. - * @param start root node - * @param reverse flag for starting at the back of the tree - */ - @SuppressWarnings("unchecked") - NodeIterator(final Node start, final boolean reverse) { - nodes = new InnerNode[8]; - poss = new int[8]; - rootSize = start.size(); - index = reverse ? rootSize : 0; - top = -1; - Node curr = start; - while(curr instanceof InnerNode) { - final InnerNode inner = (InnerNode) curr; - final int idx = reverse ? inner.arity() - 1 : 0; - if(++top == nodes.length) { - final InnerNode[] newNodes = new InnerNode[2 * top]; - System.arraycopy(nodes, 0, newNodes, 0, top); - nodes = newNodes; - final int[] newPoss = new int[2 * top]; - System.arraycopy(poss, 0, newPoss, 0, top); - poss = newPoss; - } - nodes[top] = inner; - poss[top] = idx; - curr = inner.getSub(idx); - } - leaf = (Node) curr; - leafPos = reverse ? curr.arity() : 0; - } - - @Override - public boolean hasNext() { - return index < rootSize; - } - - @Override - public int nextIndex() { - return (int) index; - } - - @Override - @SuppressWarnings("unchecked") - public E next() { - if(index >= rootSize) throw new NoSuchElementException(); - index++; - if(leafPos >= leaf.arity()) { - while(poss[top] >= nodes[top].arity() - 1) top--; - poss[top]++; - Node sub = nodes[top].getSub(poss[top]); - while(sub instanceof InnerNode) { - ++top; - nodes[top] = (InnerNode) sub; - poss[top] = 0; - sub = nodes[top].getSub(0); - } - leaf = (Node) sub; - leafPos = 0; - } - return leaf.getSub(leafPos++); - } - - @Override - public boolean hasPrevious() { - return index > 0; - } - - @Override - public int previousIndex() { - return (int) (index - 1); - } - - @Override - @SuppressWarnings("unchecked") - public E previous() { - if(index <= 0) throw new NoSuchElementException(); - index--; - if(leafPos <= 0) { - while(poss[top] <= 0) top--; - poss[top]--; - Node sub = nodes[top].getSub(poss[top]); - while(sub instanceof InnerNode) { - ++top; - nodes[top] = (InnerNode) sub; - poss[top] = sub.arity() - 1; - sub = nodes[top].getSub(poss[top]); - } - leaf = (Node) sub; - leafPos = sub.arity(); - } - return leaf.getSub(--leafPos); - } - - @Override - public void set(final E e) { - throw new UnsupportedOperationException(); - } - - @Override - public void add(final E e) { - throw new UnsupportedOperationException(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/Node.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/Node.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/Node.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/Node.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,5 @@ package org.basex.query.util.fingertree; -import java.util.*; - /** * A node inside a digit. * @@ -11,31 +9,31 @@ * @param node type * @param element type */ -public abstract class Node extends NodeLike implements Iterable { +public interface Node extends NodeLike { /** * Number of elements in this node. * @return number of elements */ - protected abstract long size(); + long size(); /** * Number of children of this node. * @return number of children */ - protected abstract int arity(); + int arity(); /** * Returns the sub-node at the given position in this node. * @param pos index of the sub-node, must be between 0 and {@link #arity()} - 1 * @return the sub-node */ - protected abstract N getSub(final int pos); + N getSub(final int pos); /** * Creates a reversed version of this node. * @return a node with the reverse order of contained elements */ - protected abstract Node reverse(); + Node reverse(); /** * Inserts the given element at the given position in this node. @@ -57,25 +55,18 @@ * @param val value to insert * @return {@code true} if the node was split, {@code false} otherwise */ - protected abstract boolean insert(Node[] siblings, final long pos, final E val); + boolean insert(Node[] siblings, final long pos, final E val); /** * Removes the element at the given position in this node. - * @param pos position of the element to remove - * @return possibly partial resulting node - */ - protected abstract NodeLike remove(final long pos); - - /** - * Removes the element at the given position in this node. Either the left or the right - * neighbor must be given for balancing. If this node is merged with one of its neighbors, the - * middle element of the result array is {@code null}. + * If this node is merged with one of its neighbors, + * the middle element of the result array is {@code null}. * @param l left neighbor, possibly {@code null} * @param r right neighbor, possibly {@code null} * @param pos position of the element to delete * @return three-element array with the new left neighbor, node and right neighbor */ - protected abstract Node[] remove(final Node l, final Node r, final long pos); + NodeLike[] remove(final Node l, final Node r, final long pos); /** * Extracts a sub-tree containing the elements at positions {@code off .. off + len - 1} @@ -85,40 +76,12 @@ * @param len number of elements * @return the sub-tree, possibly under-full */ - protected abstract NodeLike slice(final long off, final long len); - - /** - * Returns a version of this node where the first sub-node is the given one. - * @param newFirst new first sub-node - * @return resulting node - */ - protected abstract Node replaceFirst(N newFirst); - - /** - * Returns a version of this node where the last sub-node is the given one. - * @param newLast new last sub-node - * @return resulting node - */ - protected abstract Node replaceLast(N newLast); + NodeLike slice(final long off, final long len); /** * Checks that this node does not violate any invariants. * @return this node's size * @throws AssertionError if an invariant was violated */ - protected abstract long checkInvariants(); - - @Override - public final NodeIterator iterator() { - return new NodeIterator<>(this, false); - } - - /** - * Creates a {@link ListIterator} over the elements in this node. - * @param reverse flag for starting at the back of this node - * @return the list iterator - */ - public final NodeIterator listIterator(final boolean reverse) { - return new NodeIterator<>(this, reverse); - } + long checkInvariants(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/NodeLike.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/NodeLike.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/NodeLike.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/NodeLike.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,38 +9,12 @@ * @param node type * @param element type */ -public abstract class NodeLike { - /** Default constructor for package visibility. */ - NodeLike() { - } - - /** - * Concatenates this possibly partial node with the given one. - * This method is allowed to re-use the result array from a recursive call for performance. - * @param other the other node-like object - * @return a two-element array containing the one or two resulting node-like objects - */ - protected abstract NodeLike[] concat(final NodeLike other); - +public interface NodeLike { /** * Appends this possibly partial node to the given buffer. * @param nodes the buffer * @param pos number of nodes in the buffer * @return new number of nodes */ - protected abstract int append(final NodeLike[] nodes, final int pos); - - /** - * Recursive helper method for {@link #toString()}. - * @param sb string builder - * @param indent indentation depth - */ - protected abstract void toString(final StringBuilder sb, final int indent); - - @Override - public final String toString() { - final StringBuilder sb = new StringBuilder(); - toString(sb, 0); - return sb.toString(); - } + int append(final NodeLike[] nodes, final int pos); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/PartialInnerNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/PartialInnerNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/PartialInnerNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/PartialInnerNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,7 +9,7 @@ * @param node type * @param element type */ -final class PartialInnerNode extends PartialNode, E> { +final class PartialInnerNode implements NodeLike, E> { /** The sub-node. */ final NodeLike sub; @@ -22,76 +22,97 @@ } @Override - protected NodeLike, E>[] concat(final NodeLike, E> other) { - if(other instanceof PartialInnerNode) { - final PartialInnerNode single = (PartialInnerNode) other; - final NodeLike[] merged = sub.concat(single.sub); - final NodeLike a = merged[0], b = merged[1]; - - @SuppressWarnings("unchecked") - final NodeLike, E>[] out = (NodeLike, E>[]) merged; - if(b == null) { - out[0] = new PartialInnerNode<>(a); - } else { - out[0] = new InnerNode2<>((Node) a, (Node) b); - out[1] = null; - } - return out; + public int append(final NodeLike, E>[] out, final int pos) { + if(pos == 0) { + out[0] = this; + return 1; } - final InnerNode deep0 = (InnerNode) other; - final NodeLike[] merged = sub.concat(deep0.getSub(0)); - - // merging with a full node cannot result in under-full nodes - final Node a = (Node) merged[0], b = (Node) merged[1]; @SuppressWarnings("unchecked") - final NodeLike, E>[] out = (NodeLike, E>[]) merged; - - if(other instanceof InnerNode2) { - final InnerNode2 deep = (InnerNode2) other; - if(b == null) { - out[0] = new InnerNode2<>(a, deep.child1); - } else { - out[0] = new InnerNode3<>(a, b, deep.child1); - out[1] = null; - } - } else { - final InnerNode3 deep = (InnerNode3) other; - if(b == null) { - out[0] = new InnerNode3<>(a, deep.child1, deep.child2); + final NodeLike[] buffer = (NodeLike[]) out; + final NodeLike, E> left = out[pos - 1]; + if(left instanceof PartialInnerNode) { + buffer[pos - 1] = ((PartialInnerNode) left).sub; + if(sub.append(buffer, pos) == pos) { + out[pos - 1] = new PartialInnerNode<>(buffer[pos - 1]); } else { - out[0] = new InnerNode2<>(a, b); - out[1] = new InnerNode2<>(deep.child1, deep.child2); + @SuppressWarnings("unchecked") + final Node[] ch = new Node[2]; + ch[0] = (Node) buffer[pos - 1]; + ch[1] = (Node) buffer[pos]; + out[pos - 1] = new InnerNode<>(ch); + out[pos] = null; } + return pos; } - return out; - } - @Override - protected int append(final NodeLike, E>[] nodes, final int pos) { - if(pos == 0) { - nodes[pos] = this; - return 1; + final Node[] children = ((InnerNode) left).children; + final int n = children.length; + final Node a, b; + if(sub instanceof Node) { + a = children[n - 1]; + b = (Node) sub; + } else { + buffer[pos - 1] = children[n - 1]; + if(sub.append(buffer, pos) == pos) { + final Node[] ch = children.clone(); + ch[n - 1] = (Node) buffer[pos - 1]; + out[pos - 1] = new InnerNode<>(ch); + return pos; + } + a = (Node) buffer[pos - 1]; + b = (Node) buffer[pos]; } - final NodeLike, E>[] joined = nodes[pos - 1].concat(this); - nodes[pos - 1] = joined[0]; - - if(joined[1] != null) { - nodes[pos] = joined[1]; - return pos + 1; + if(n < FingerTree.MAX_ARITY) { + @SuppressWarnings("unchecked") + final Node[] ch = new Node[n + 1]; + System.arraycopy(children, 0, ch, 0, n - 1); + ch[n - 1] = a; + ch[n] = b; + out[pos - 1] = new InnerNode<>(ch); + out[pos] = null; + return pos; } - return pos; + final int ll = (n + 1) / 2, rl = n + 1 - ll; + @SuppressWarnings("unchecked") + final Node[] ls = new Node[ll], rs = new Node[rl]; + System.arraycopy(children, 0, ls, 0, ll); + System.arraycopy(children, ll, rs, 0, rl - 2); + rs[rl - 2] = a; + rs[rl - 1] = b; + out[pos - 1] = new InnerNode<>(ls); + out[pos] = new InnerNode<>(rs); + return pos + 1; } @Override - protected void toString(final StringBuilder sb, final int indent) { - for(int i = 0; i < indent; i++) sb.append(" "); - sb.append("OneSubnode[\n"); - sub.toString(sb, indent + 1); - sb.append('\n'); - for(int i = 0; i < indent; i++) sb.append(" "); + public String toString() { + final StringBuilder sb = new StringBuilder(); + toString(sb, 0); + return sb.toString(); + } + + /** + * Recursive helper for {@link #toString()}. + * @param sb string builder + * @param indent indentation level + */ + void toString(final StringBuilder sb, final int indent) { + for(int i = 0; i < indent; i++) sb.append(' ').append(' '); + sb.append(this.getClass().getSimpleName()).append('[').append('\n'); + if(sub instanceof InnerNode) { + ((InnerNode) sub).toString(sb, indent + 1); + } else if(sub instanceof PartialInnerNode) { + ((PartialInnerNode) sub).toString(sb, indent + 1); + } else { + for(final String line : sub.toString().split("\r\n?|\n")) { + for(int i = 0; i <= indent; i++) sb.append(' ').append(' '); + sb.append(line).append('\n'); + } + } + for(int i = 0; i < indent; i++) sb.append(' ').append(' '); sb.append(']'); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/PartialNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/PartialNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/PartialNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/PartialNode.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -package org.basex.query.util.fingertree; - -/** - * A partial node containing less than two children. - * - * @author BaseX Team 2005-15, BSD License - * @author Leo Woerteler - * - * @param node type - * @param element type - */ -public abstract class PartialNode extends NodeLike { - -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/Single.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/Single.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/Single.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/Single.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,156 +0,0 @@ -package org.basex.query.util.fingertree; - -import java.util.*; - -/** - * A tree consisting of a single value. - * - * @author BaseX Team 2005-15, BSD License - * @author Leo Woerteler - * - * @param node type - * @param element type - */ -final class Single extends FingerTree { - /** The element. */ - final Node elem; - - /** - * Constructor. - * @param elem element - */ - public Single(final Node elem) { - this.elem = elem; - } - - @Override - public Deep cons(final Node fst) { - final long leftSize = fst.size(); - @SuppressWarnings("unchecked") - final Node[] left = new Node[] { fst }, right = new Node[] { elem }; - return Deep.get(left, leftSize, right, leftSize + elem.size()); - } - - @Override - public Deep snoc(final Node lst) { - final long leftSize = elem.size(); - @SuppressWarnings("unchecked") - final Node[] left = new Node[] { elem }, right = new Node[] { lst }; - return Deep.get(left, leftSize, right, leftSize + lst.size()); - } - - @Override - public Node head() { - return elem; - } - - @Override - public Node last() { - return elem; - } - - @Override - public FingerTree init() { - return Empty.getInstance(); - } - - @Override - public FingerTree tail() { - return Empty.getInstance(); - } - - @Override - public long size() { - return elem.size(); - } - - @Override - public FingerTree concat(final Node[] mid, final FingerTree other) { - return other.isEmpty() ? addAll(mid, false) : other.addAll(mid, true).cons(elem); - } - - @Override - public FingerTree reverse() { - return new Single<>(elem.reverse()); - } - - @Override - public FingerTree insert(final long pos, final E val) { - @SuppressWarnings("unchecked") - final Node[] siblings = new Node[4]; - if(!elem.insert(siblings, pos, val)) { - // node was not split - return new Single<>(siblings[1]); - } - - final Node l = siblings[1], r = siblings[2]; - @SuppressWarnings("unchecked") - final Node[] left = new Node[] { l }, right = new Node[] { r }; - return Deep.get(left, l.size(), right, elem.size() + 1); - } - - @Override - public TreeSlice remove(final long pos) { - final NodeLike removed = elem.remove(pos); - return removed instanceof Node ? new TreeSlice<>(new Single<>((Node) removed)) - : new TreeSlice<>((PartialNode) removed); - } - - @Override - public TreeSlice slice(final long pos, final long len) { - if(pos == 0 && len == elem.size()) return new TreeSlice<>(this); - final NodeLike sub = elem.slice(pos, len); - if(sub instanceof Node) return new TreeSlice<>(new Single<>((Node) sub)); - return new TreeSlice<>((PartialNode) sub); - } - - @Override - public ListIterator listIterator(final boolean reverse) { - return elem.listIterator(reverse); - } - - @Override - FingerTree addAll(final Node[] nodes, final boolean left) { - if(nodes.length == 0) return this; - if(nodes.length <= 4) { - @SuppressWarnings("unchecked") - final Node[] arr = new Node[] { elem }; - return Deep.get(left ? nodes : arr, left ? arr : nodes); - } - - final FingerTree tree = buildTree(nodes, nodes.length, Deep.size(nodes)); - return left ? tree.snoc(elem) : tree.cons(elem); - } - - @Override - public FingerTree replaceHead(final Node head) { - return new Single<>(head); - } - - @Override - public FingerTree replaceLast(final Node head) { - return new Single<>(head); - } - - @Override - void toString(final StringBuilder sb, final int indent) { - for(int i = 0; i < indent; i++) sb.append(" "); - sb.append("Single[\n"); - elem.toString(sb, indent + 1); - sb.append("\n"); - for(int i = 0; i < indent; i++) sb.append(" "); - sb.append("]"); - } - - @Override - public long checkInvariants() { - return elem.checkInvariants(); - } - - @Override - public long[] sizes(final int depth) { - final long[] sizes = new long[depth + 1]; - sizes[depth] = elem.size(); - return sizes; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/SingletonTree.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/SingletonTree.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/SingletonTree.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/SingletonTree.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,140 @@ +package org.basex.query.util.fingertree; + +/** + * A tree consisting of a single value. + * + * @author BaseX Team 2005-15, BSD License + * @author Leo Woerteler + * + * @param node type + * @param element type + */ +final class SingletonTree extends FingerTree { + /** The element. */ + final Node elem; + + /** + * Constructor. + * @param elem element + */ + public SingletonTree(final Node elem) { + this.elem = elem; + } + + @Override + public DeepTree cons(final Node fst) { + final long leftSize = fst.size(); + @SuppressWarnings("unchecked") + final Node[] left = new Node[] { fst }, right = new Node[] { elem }; + return DeepTree.get(left, leftSize, right, leftSize + elem.size()); + } + + @Override + public DeepTree snoc(final Node lst) { + final long leftSize = elem.size(); + @SuppressWarnings("unchecked") + final Node[] left = new Node[] { elem }, right = new Node[] { lst }; + return DeepTree.get(left, leftSize, right, leftSize + lst.size()); + } + + @Override + public Node head() { + return elem; + } + + @Override + public Node last() { + return elem; + } + + @Override + public FingerTree init() { + return EmptyTree.getInstance(); + } + + @Override + public FingerTree tail() { + return EmptyTree.getInstance(); + } + + @Override + public long size() { + return elem.size(); + } + + @Override + public FingerTree concat(final Node[] mid, final long sz, + final FingerTree other) { + return other.isEmpty() ? addAll(mid, sz, false) : other.addAll(mid, sz, true).cons(elem); + } + + @Override + public FingerTree reverse() { + return new SingletonTree<>(elem.reverse()); + } + + @Override + public FingerTree insert(final long pos, final E val) { + @SuppressWarnings("unchecked") + final Node[] siblings = new Node[4]; + if(!elem.insert(siblings, pos, val)) { + // node was not split + return new SingletonTree<>(siblings[1]); + } + + final Node l = siblings[1], r = siblings[2]; + @SuppressWarnings("unchecked") + final Node[] left = new Node[] { l }, right = new Node[] { r }; + return DeepTree.get(left, l.size(), right, elem.size() + 1); + } + + @Override + public TreeSlice remove(final long pos) { + final NodeLike[] removed = elem.remove(null, null, pos); + return new TreeSlice<>(removed[1]); + } + + @Override + public TreeSlice slice(final long pos, final long len) { + if(pos == 0 && len == elem.size()) return new TreeSlice<>(this); + return new TreeSlice<>(elem.slice(pos, len)); + } + + @Override + FingerTree addAll(final Node[] nodes, final long sz, final boolean left) { + if(nodes.length == 0) return this; + if(nodes.length <= MAX_DIGIT) { + @SuppressWarnings("unchecked") + final Node[] arr = new Node[] { elem }; + return left ? DeepTree.get(nodes, arr) : DeepTree.get(arr, nodes); + } + + final FingerTree tree = buildTree(nodes, nodes.length, sz); + return left ? tree.snoc(elem) : tree.cons(elem); + } + + @Override + public FingerTree replaceHead(final Node head) { + return new SingletonTree<>(head); + } + + @Override + public FingerTree replaceLast(final Node head) { + return new SingletonTree<>(head); + } + + @Override + void toString(final StringBuilder sb, final int indent) { + for(int i = 0; i < indent; i++) sb.append(" "); + sb.append("Single[\n"); + toString(elem, sb, indent + 1); + sb.append("\n"); + for(int i = 0; i < indent; i++) sb.append(" "); + sb.append("]"); + } + + @Override + public long checkInvariants() { + return elem.checkInvariants(); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/TreeSlice.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/TreeSlice.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/fingertree/TreeSlice.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/fingertree/TreeSlice.java 2015-07-14 10:54:40.000000000 +0000 @@ -14,24 +14,22 @@ /** A full sub-tree. */ private FingerTree tree; /** A partial node. */ - private PartialNode partial; + private NodeLike partial; /** * Constructor for whole trees. * @param tree the tree */ TreeSlice(final FingerTree tree) { - this.tree = tree; - this.partial = null; + setTree(tree); } /** - * Constructor for partial nodes. + * Constructor for potentially partial nodes. * @param partial partial node */ - TreeSlice(final PartialNode partial) { - this.tree = null; - this.partial = partial; + TreeSlice(final NodeLike partial) { + setNodeLike(partial); } /** @@ -57,7 +55,7 @@ * returns {@code false}. * @return the contained partial node */ - public PartialNode getPartial() { + public NodeLike getPartial() { return partial; } @@ -77,17 +75,22 @@ } /** - * Sets the contents of this slice to the given partial node and returns it with the correct type. + * Sets the contents of this slice to the given node and returns it with the correct type. * The value with the current type is invalid afterwards and should not be used. * @param new node type - * @param newPartial new contents + * @param newNode new contents * @return type-cast version of this slice */ - TreeSlice setPartial(final PartialNode newPartial) { + TreeSlice setNodeLike(final NodeLike newNode) { @SuppressWarnings("unchecked") final TreeSlice out = (TreeSlice) this; - out.partial = newPartial; - out.tree = null; + if(newNode instanceof Node) { + out.partial = null; + out.tree = new SingletonTree<>((Node) newNode); + } else { + out.partial = newNode; + out.tree = null; + } return out; } @@ -100,14 +103,10 @@ * @return type-cast version of this slice */ TreeSlice setNodes(final NodeLike[] arr, final int n, final long size) { - if(n == 1) { - if(arr[0] instanceof PartialNode) return setPartial((PartialNode) arr[0]); - final Node node = (Node) arr[0]; - return setTree(new Single<>(node)); - } - + if(n == 0) return setTree(EmptyTree.getInstance()); + if(n == 1) return setNodeLike(arr[0]); final int mid = n / 2; - final Node[] left = Deep.slice(arr, 0, mid), right = Deep.slice(arr, mid, n); - return setTree(Deep.get(left, right, size)); + final Node[] left = DeepTree.slice(arr, 0, mid), right = DeepTree.slice(arr, mid, n); + return setTree(DeepTree.get(left, right, size)); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/format/DecFormatter.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/format/DecFormatter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/format/DecFormatter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/format/DecFormatter.java 2015-07-14 10:54:40.000000000 +0000 @@ -102,14 +102,20 @@ // check for duplicate characters zero = z; final IntSet is = new IntSet(); - final int[] ss = { decimal, grouping, exponent, percent, permille, zero, optional, pattern }; + for(int i = 0; i < 10; i++) is.add(zero + i); + final int[] ss = { decimal, grouping, exponent, percent, permille, optional, pattern }; for(final int s : ss) if(!is.add(s)) throw DUPLDECFORM_X.get(info, (char) s); // create auxiliary strings final TokenBuilder tb = new TokenBuilder(); for(int i = 0; i < 10; i++) tb.add(zero + i); digits = tb.toArray(); - actives = tb.add(decimal).add(grouping).add(optional).add(exponent).finish(); + // "decimal-separator-sign, exponent-separator-sign, grouping-sign, decimal-digit-family, + // optional-digit-sign and pattern-separator-sign are classified as active characters" + // -> decimal-digit-family: added above. pattern-separator-sign: will never occur at this stage + actives = tb.add(decimal).add(exponent).add(grouping).add(optional).finish(); + // "all other characters (including the percent-sign and per-mille-sign) are classified + // as passive characters." } /** @@ -126,19 +132,22 @@ // find pattern separator and sub-patterns final TokenList tl = new TokenList(); byte[] pic = picture; + // "A picture-string consists either of a sub-picture, or of two sub-pictures separated by + // a pattern-separator-sign" final int i = indexOf(pic, pattern); if(i == -1) { tl.add(pic); } else { tl.add(substring(pic, 0, i)); pic = substring(pic, i + cl(pic, i)); + // "A picture-string must not contain more than one pattern-separator-sign" if(contains(pic, pattern)) throw PICNUM_X.get(info, picture); tl.add(pic); } final byte[][] patterns = tl.finish(); // check and analyze patterns - if(!check(patterns)) throw PICNUM_X.get(info, picture); + if(!checkSyntax(patterns)) throw PICNUM_X.get(info, picture); final Picture[] pics = analyze(patterns); // return formatted string @@ -150,69 +159,102 @@ * @param patterns patterns * @return result of check */ - private boolean check(final byte[][] patterns) { + private boolean checkSyntax(final byte[][] patterns) { for(final byte[] pt : patterns) { - boolean frac = false, pas = false, act = false, exp = false; - boolean dg = false, opt1 = false, opt2 = false; - int cl, pc = 0, pm = 0, ls = 0; + boolean frac = false, act = false, expAct = false, exp = false, digMant = false; + boolean optInt = false, optFrac = false, per = false; + int cl, last = 0; // loop through all characters final int pl = pt.length; for(int i = 0; i < pl; i += cl) { final int ch = ch(pt, i); cl = cl(pt, i); - boolean active = contains(actives, ch); final boolean digit = contains(digits, ch); - if(exp && !digit) return false; + boolean active = contains(actives, ch), expon = false; if(ch == decimal) { - // more than 1 decimal sign? + // "A sub-picture must not contain more than one decimal-separator-sign." if(frac) return false; frac = true; } else if(ch == grouping) { - // adjacent decimal sign? - if(i == 0 && frac || ls == decimal || i + cl < pl ? ch(pt, i + cl) == decimal : !frac) + // "A sub-picture must not contain a grouping-separator-sign that appears adjacent to a + // decimal-separator-sign, or in the absence of a decimal-separator-sign, at the end of + // the integer part." + if(i == 0 && frac || last == decimal || (i + cl < pl ? ch(pt, i + cl) == decimal : !frac)) return false; + // "A sub-picture must not contain two adjacent grouping-separator-signs." + if(last == grouping) return false; } else if(ch == exponent) { - if(contains(actives, ls) && contains(actives, ch(pt, i + cl))) { - // more than one exponent sign + // "A character that matches the chosen exponent-separator-sign is treated as an + // exponent-separator-sign if it is both preceded and followed within the sub-picture by + // an active character." + if(act && containsActive(pt, i + cl)) { + // "A sub-picture must not contain more than one character that is treated as an + // exponent-separator-sign." if(exp) return false; - exp = true; + expon = true; } else { + // "Otherwise, it is treated as a passive character." active = false; } - } else if(ch == percent) { - ++pc; - } else if(ch == permille) { - ++pm; + } else if(ch == percent || ch == permille) { + // "A sub-picture must not contain more than one percent-sign or per-mille-sign, + // and it must not contain one of each." + if(per) return false; + per = true; } else if(ch == optional) { if(frac) { - opt2 = true; + optFrac = true; } else { - // integer part, and optional sign after digit? - if(dg) return false; - opt1 = true; + // "The integer part of a sub-picture must not contain a member of the decimal-digit- + // family that is followed by an optional-digit-sign." + if(digMant) return false; + optInt = true; } } else if(digit) { - // fractional part, and digit after optional sign? - if(frac && opt2) return false; - dg = true; + if(!exp) { + // "The fractional part of a sub-picture must not contain an optional-digit-sign that + // is followed by a member of the decimal-digit-family." + if(optFrac) return false; + digMant = true; + } + } + + if(active) { + // "If a sub-picture contains a character treated as an exponent-separator-sign then + // this must be followed by one or more characters that are members of the + // decimal-digit-family, and it must not be followed by any active character that is not + // a member of the decimal-digit-family." (*) + if(exp) { + if(!digit) return false; + expAct = true; + } + act = true; + } else { + // "A sub-picture must not contain a passive character that is preceded by an active + // character and that is followed by another active character." + if(act && containsActive(pt, i + cl)) return false; } - // passive character with preceding and following active character? - if(active && pas && act) return false; - // will be assigned if active characters were found - if(act) pas |= !active; - act |= active; // cache last character - ls = ch; + last = ch; + if(expon) exp = true; } - // percent and permille sign: more than 1, or exponent sign? - if(pc + pm > (exp ? 0 : 1)) return false; - // no optional sign or digit? - if(!opt1 && !opt2 && !dg) return false; + // "The mantissa part of a sub-picture must contain at least one character that is an + // optional-digit-sign or a member of the decimal-digit-family." + if(!optInt && !optFrac && !digMant) return false; + + // "A sub-picture that contains a percent-sign or per-mille-sign must not contain a character + // treated as an exponent-separator-sign." + if(per && exp) return false; + + // (*) continued + if(exp && !expAct) return false; } + + // everything ok return true; } @@ -223,11 +265,11 @@ */ private Picture[] analyze(final byte[][] patterns) { // pictures - final int pl = patterns.length; - final Picture[] pics = new Picture[pl]; + final int picL = patterns.length; + final Picture[] pics = new Picture[picL]; // analyze patterns - for(int p = 0; p < pl; p++) { + for(int p = 0; p < picL; p++) { final byte[] pt = patterns[p]; final Picture pic = new Picture(); @@ -241,8 +283,8 @@ final int[] opt = new int[2]; // loop through all characters - final int ptl = pt.length; - for(int i = 0, cl; i < ptl; i += cl) { + final int pl = pt.length; + for(int i = 0, cl; i < pl; i += cl) { final int ch = ch(pt, i); cl = cl(pt, i); boolean active = contains(actives, ch); @@ -253,15 +295,9 @@ } else if(ch == optional) { opt[pos]++; } else if(ch == exponent) { - // check if following characters are all digits - boolean e = true; - for(int c = i + cl; c < ptl && e; c += cl(pt, c)) e = contains(digits, ch(pt, c)); - if(e && i + cl < ptl) { - // test succeeds, exponent sign found + if(act && containsActive(pt, i + cl)) { exp = 0; } else { - // passive character: add to prefixes/suffixes - pic.xyzfix[pos == 0 && act ? pos + 1 : pos].add(ch); active = false; } } else if(ch == grouping) { @@ -269,14 +305,17 @@ } else if(contains(digits, ch)) { if(exp == -1) pic.min[pos]++; else exp++; + } + + if(active) { + act = true; } else { // passive characters pic.pc |= ch == percent; pic.pm |= ch == permille; // prefixes/suffixes - pic.xyzfix[pos == 0 && act ? pos + 1 : pos].add(ch); + pic.prefSuf[pos == 0 && act ? pos + 1 : pos].add(ch); } - act |= active; } // finalize integer-part-grouping-positions final int[] igp = pic.group[0]; @@ -300,6 +339,19 @@ } /** + * Checks if the specified pattern contains active characters after the specified index. + * @param pt pattern + * @param i index + * @return result of check + */ + private boolean containsActive(final byte[] pt, final int i) { + for(int p = i; p < pt.length; p += cl(pt, p)) { + if(contains(actives, ch(pt, p))) return true; + } + return false; + } + + /** * Formats the specified number and returns a string representation. * @param it item * @param pics pictures @@ -310,44 +362,48 @@ private byte[] format(final ANum it, final Picture[] pics, final InputInfo ii) throws QueryException { - // return results for NaN + // Rule 1: return results for NaN final double d = it.dbl(ii); if(Double.isNaN(d)) return nan; - // return infinite results + // Rule 2: check if value if negative (smaller than zero or -0) final boolean neg = d < 0 || d == 0 && Double.doubleToLongBits(d) == Long.MIN_VALUE; final Picture pic = pics[neg && pics.length == 2 ? 1 : 0]; - final TokenBuilder res = new TokenBuilder(); - final TokenBuilder intgr = new TokenBuilder(); - final TokenBuilder fract = new TokenBuilder(); + final IntList res = new IntList(), intgr = new IntList(), fract = new IntList(); int exp = 0; - if(d == Double.POSITIVE_INFINITY || d == Double.NEGATIVE_INFINITY) { - intgr.add(inf); + if(Double.isInfinite(d)) { + // Rule 3 + intgr.add(new TokenParser(inf).toArray()); } else { // convert and round number ANum num = it; + // Rule 4 if(pic.pc) num = (ANum) Calc.MULT.ev(ii, num, Int.get(100)); if(pic.pm) num = (ANum) Calc.MULT.ev(ii, num, Int.get(1000)); - if(pic.minExp != 0) { - final String s = (num instanceof Dbl || num instanceof Flt ? - Dec.get(num.dbl(ii)) : num).abs().toString(); - final int sep = s.indexOf('.'); - final int i = sep == -1 ? s.length() : sep; - final int m = pic.min[0]; - double n = 1; - exp = i - m; - if(exp > 0) { - for(int a = exp; a-- > 0;) n *= 10; - num = (ANum) Calc.DIV.ev(ii, num, Dec.get(n)); + // Rule 5 + if(pic.minExp != 0 && d != 0) { + BigDecimal dec = num.dec(ii).abs().stripTrailingZeros(); + int scl = 0; + if(dec.compareTo(BigDecimal.ONE) >= 0) { + scl = dec.setScale(0, RoundingMode.HALF_DOWN).precision(); } else { - for(int a = -exp; a-- > 0;) n *= 10; - num = (ANum) Calc.MULT.ev(ii, num, Dec.get(n)); + while(dec.compareTo(BigDecimal.ONE) < 0) { + dec = dec.multiply(BigDecimal.TEN); + scl--; + } + scl++; + } + + exp = scl - pic.min[0]; + if(exp != 0) { + final BigDecimal n = BigDecimal.TEN.pow(Math.abs(exp)); + num = (ANum) Calc.MULT.ev(ii, num, Dec.get(exp > 0 ? BigDecimal.ONE.divide(n) : n)); } } num = num.round(pic.maxFrac, true).abs(); - // convert positive number to string, chop leading zero + // convert positive number to string final String s = (num instanceof Dbl || num instanceof Flt ? Dec.get(BigDecimal.valueOf(num.dbl(ii))) : num).toString(); @@ -358,14 +414,14 @@ final int sl = s.length(); final int il = sep == -1 ? sl : sep; for(int i = il; i < pic.min[0]; ++i) intgr.add(zero); - for(int i = 0; i < il; i++) intgr.add(zero + s.charAt(i) - '0'); + // fractional number: skip leading 0 + if(!s.startsWith("0.")) for(int i = 0; i < il; i++) intgr.add(zero + s.charAt(i) - '0'); // squeeze in grouping separators - if(pic.group[0].length == 1) { + if(pic.group[0].length == 1 && pic.group[0][0] > 0) { // regular pattern with repeating separators - final int pos = pic.group[0][0]; for(int p = intgr.size() - (neg ? 2 : 1); p > 0; --p) { - if(p % pos == 0) intgr.insert(intgr.size() - p, grouping); + if(p % pic.group[0][0] == 0) intgr.insert(intgr.size() - p, grouping); } } else { // irregular pattern, or no separators at all @@ -392,11 +448,9 @@ // add minus sign if(neg && pics.length != 2) res.add(minus); // add prefix and integer part - res.add(pic.xyzfix[0].toArray()).add(intgr.finish()); + res.add(pic.prefSuf[0].toArray()).add(intgr.finish()); // add fractional part if(!fract.isEmpty()) res.add(decimal).add(fract.finish()); - // add suffix - res.add(pic.xyzfix[1].toArray()); // add exponent if(pic.minExp != 0) { res.add(exponent); @@ -406,13 +460,15 @@ for(int i = sl; i < pic.minExp; i++) res.add(zero); for(int i = 0; i < sl; i++) res.add(zero + s.charAt(i) - '0'); } - return res.finish(); + // add suffix + res.add(pic.prefSuf[1].toArray()); + return new TokenBuilder(res.finish()).finish(); } /** Picture variables. */ private static final class Picture { /** Prefix/suffix. */ - private final TokenBuilder[] xyzfix = { new TokenBuilder(), new TokenBuilder() }; + private final IntList[] prefSuf = { new IntList(), new IntList() }; /** Integer/fractional-part-grouping-positions. */ private final int[][] group = { {}, {} }; /** Minimum-integer/fractional-part-size. */ diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/format/Formatter.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/format/Formatter.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/format/Formatter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/format/Formatter.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,7 +5,6 @@ import java.math.*; import java.util.*; -import java.util.regex.*; import org.basex.query.*; import org.basex.query.value.item.*; @@ -21,8 +20,6 @@ * @author Christian Gruen */ public abstract class Formatter extends FormatUtil { - /** Calendar pattern. */ - private static final Pattern CALENDAR = Pattern.compile("(Q\\{([^}]*)\\})?([^}]+)"); /** Military timezones. */ private static final byte[] MIL = token("YXWVUTSRQPONZABCDEFGHIKLM"); /** Token: Nn. */ @@ -132,10 +129,8 @@ if(lng.length != 0 && MAP.get(lng) == null) tb.add("[Language: en]"); boolean iso = false; if(cal.length != 0) { - final Matcher m = CALENDAR.matcher(string(cal)); - if(!m.matches()) throw CALQNAME_X.get(ii, cal); - final QNm qnm = new QNm(m.group(3), m.group(1) == null || - m.group(2).isEmpty() ? null : m.group(2)); + final QNm qnm = QNm.resolve(cal); + if(qnm == null) throw CALQNAME_X.get(ii, cal); if(!qnm.hasURI()) { int c = -1; final byte[] ln = qnm.local(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/ft/FTMatches.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/ft/FTMatches.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/ft/FTMatches.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/ft/FTMatches.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,130 @@ +package org.basex.query.util.ft; + +import java.util.*; + +import org.basex.util.*; +import org.basex.util.list.*; + +/** + * AllMatches full-text container, referencing several {@link FTMatch} instances. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class FTMatches extends ElementList implements Iterable { + /** Full-text matches. */ + public FTMatch[] match = {}; + /** Position of a token in the query. */ + public int pos; + + /** + * Constructor. + */ + public FTMatches() { + } + + /** + * Constructor. + * @param pos query position + */ + public FTMatches(final int pos) { + this.pos = pos; + } + + /** + * Resets the match container. + * @param ps query position + */ + public void reset(final int ps) { + pos = ps; + size = 0; + } + + /** + * Adds a match entry. + * @param ps position + */ + public void or(final int ps) { + or(ps, ps); + } + + /** + * Adds a match entry. + * @param start start position + * @param end end position + */ + public void or(final int start, final int end) { + add(new FTMatch(1).add(new FTStringMatch(start, end, pos))); + } + + /** + * Adds a match entry. + * @param start start position + * @param end end position + */ + public void and(final int start, final int end) { + final FTStringMatch sm = new FTStringMatch(start, end, pos); + for(final FTMatch m : this) m.add(sm); + } + + /** + * Adds a match entry. + * @param ftm match to be added + */ + public void add(final FTMatch ftm) { + if(size == match.length) match = Array.copy(match, new FTMatch[Array.newSize(size)]); + match[size++] = ftm; + } + + /** + * Removes the specified match. + * @param index match index + */ + public void delete(final int index) { + Array.move(match, index + 1, -1, --size - index); + } + + /** + * Checks if at least one of the matches contains only includes. + * @return result of check + */ + public boolean matches() { + for(final FTMatch m : this) if(m.match()) return true; + return false; + } + + /** + * Combines two matches as phrase. + * @param all second match list + * @param distance word distance + * @return true if matches are left + */ + public boolean phrase(final FTMatches all, final int distance) { + int a = 0, b = 0, c = 0; + while(a < size && b < all.size) { + final int e = all.match[b].match[0].start; + final int d = e - match[a].match[0].end - distance; + if(d == 0) { + match[c] = match[a]; + match[c++].match[0].end = e; + } + if(d >= 0) ++a; + if(d <= 0) ++b; + } + size = c; + return size != 0; + } + + @Override + public Iterator iterator() { + return new ArrayIterator<>(match, size); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append(Util.className(this)).append('[').append(pos).append(']'); + for(final FTMatch m : this) sb.append("\n ").append(m); + return sb.toString(); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/ft/FTMatch.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/ft/FTMatch.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/ft/FTMatch.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/ft/FTMatch.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,95 @@ +package org.basex.query.util.ft; + +import java.util.*; + +import org.basex.util.*; +import org.basex.util.list.*; + +/** + * Match full-text container, referencing several {@link FTStringMatch} instances. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class FTMatch extends ElementList implements Iterable { + /** String matches. */ + FTStringMatch[] match; + + /** + * Constructor. + */ + public FTMatch() { + this(0); + } + + /** + * Constructor, specifying an initial internal array size. + * @param capacity initial array capacity + */ + public FTMatch(final int capacity) { + match = new FTStringMatch[capacity]; + } + + /** + * Adds all matches of a full-text match. + * @param ftm match to be added + * @return self reference + */ + public FTMatch add(final FTMatch ftm) { + for(final FTStringMatch sm : ftm) add(sm); + return this; + } + + /** + * Adds a single string match. + * @param ftm match to be added + * @return self reference + */ + public FTMatch add(final FTStringMatch ftm) { + if(size == match.length) match = Array.copy(match, new FTStringMatch[newSize()]); + match[size++] = ftm; + return this; + } + + /** + * Checks if the full-text match is not part of the specified match. + * @param ftm match to be checked + * @return result of check + */ + public boolean notin(final FTMatch ftm) { + for(final FTStringMatch s : this) { + for(final FTStringMatch sm : ftm) if(!s.in(sm)) return true; + } + return false; + } + + /** + * Checks if the match contains no string excludes. + * @return result of check + */ + boolean match() { + for(final FTStringMatch s : this) if(s.exclude) return false; + return true; + } + + /** + * Sorts the matches by their start and end positions. + */ + public void sort() { + Arrays.sort(match, 0, size, null); + } + + @Override + public Iterator iterator() { + return new ArrayIterator<>(match, size); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + for(final FTStringMatch s : this) { + sb.append(sb.length() == 0 ? "" : ", ").append(s); + } + return Util.className(this) + ' ' + sb; + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/ft/FTPosData.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/ft/FTPosData.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/ft/FTPosData.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/ft/FTPosData.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,113 @@ +package org.basex.query.util.ft; + +import java.util.*; + +import org.basex.data.*; +import org.basex.util.*; +import org.basex.util.hash.*; +import org.basex.util.list.*; + +/** + * This class provides a container for query full-text positions, + * which is evaluated by the visualizations. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + * @author Sebastian Gath + */ +public final class FTPosData { + /** Position references. */ + private FTPos[] pos = new FTPos[1]; + /** Data reference. */ + private Data dt; + /** Number of values. */ + private int size; + + /** + * Adds position data. + * + * @param data data reference + * @param pre pre value + * @param all full-text matches + */ + public void add(final Data data, final int pre, final FTMatches all) { + if(dt == null) dt = data; + else if(dt != data) return; + + // cache all positions + final IntSet set = new IntSet(); + for(final FTMatch ftm : all) { + for(final FTStringMatch sm : ftm) { + for(int s = sm.start; s <= sm.end; ++s) set.add(s); + } + } + + // sort and store all positions + final IntList il = new IntList(set.toArray()).sort(); + int c = find(pre); + if(c < 0) { + c = -c - 1; + if(size == pos.length) pos = Arrays.copyOf(pos, Array.newSize(size)); + Array.move(pos, c, 1, size++ - c); + pos[c] = new FTPos(pre, il); + } else { + pos[c].union(il); + } + } + + /** + * Gets full-text data from the container. + * If no data is stored for a pre value, {@code null} is returned. + * int[0] : [pos0, ..., posn] + * int[1] : [poi0, ..., poin] + * @param data data reference + * @param pre int pre value + * @return int[2][n] full-text data or {@code null} + */ + public FTPos get(final Data data, final int pre) { + final int p = find(pre); + return p < 0 || dt != data ? null : pos[p]; + } + + /** + * Returns the number of entries. + * @return size + */ + public int size() { + int c = 0; + for(int i = 0; i < size; ++i) c += pos[i].size(); + return c; + } + + /** + * Compares full-text position data. + * @param ft data to be compared + * @return result of check + */ + public boolean sameAs(final FTPosData ft) { + if(size != ft.size) return false; + for(int i = 0; i < size; ++i) { + if(pos[i].pre != ft.pos[i].pre || !Arrays.equals( + pos[i].list.toArray(), ft.pos[i].list.toArray())) return false; + } + return true; + } + + /** + * Returns the index of the specified pre value. + * @param pre int pre value + * @return index, or negative index - 1 if pre value is not found + */ + private int find(final int pre) { + // binary search + int l = 0, h = size - 1; + while(l <= h) { + final int m = l + h >>> 1; + final int c = pos[m].pre - pre; + if(c == 0) return m; + if(c < 0) l = m + 1; + else h = m - 1; + } + return -l - 1; + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/ft/FTPos.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/ft/FTPos.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/ft/FTPos.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/ft/FTPos.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,61 @@ +package org.basex.query.util.ft; + +import org.basex.util.hash.*; +import org.basex.util.list.*; + +/** + * This class contains full-text positions for a single database node. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class FTPos { + /** Pre value. */ + final int pre; + /** Positions. */ + IntList list; + + /** + * Constructor. + * @param pre pre value + * @param list sorted positions + */ + FTPos(final int pre, final IntList list) { + this.pre = pre; + this.list = list; + } + + /** + * Merges the specified position arrays. + * @param pos sorted positions + */ + void union(final IntList pos) { + final int ps = list.size(), ls = pos.size(); + final IntSet set = new IntSet(ps + ls); + for(int p = 0, s = ps; p < s; p++) set.add(list.get(p)); + for(int l = 0, s = ls; l < s; l++) set.add(pos.get(l)); + list = new IntList(set.toArray()).sort(); + } + + /** + * Checks if the specified position is found. + * @param pos position to be found + * @return result of check + */ + public boolean contains(final int pos) { + return list.sortedIndexOf(pos) >= 0; + } + + /** + * Returns the number of positions. + * @return number of positions + */ + public int size() { + return list.size(); + } + + @Override + public String toString() { + return pre + ": " + list; + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/ft/FTStringMatch.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/ft/FTStringMatch.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/ft/FTStringMatch.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/ft/FTStringMatch.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,67 @@ +package org.basex.query.util.ft; + +/** + * Single full-text string match. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class FTStringMatch implements Comparable { + /** Position of the token in the query. */ + public final int pos; + /** Start position. */ + public final int start; + /** End position. */ + public int end; + /** Exclude flag. */ + public boolean exclude; + /** Gaps (non-contiguous) flag. */ + public boolean gaps; + + /** + * Constructor. + * @param start start position + * @param end end position + * @param pos query pos + */ + FTStringMatch(final int start, final int end, final int pos) { + this.start = start; + this.end = end; + this.pos = pos; + } + + /** + * Checks if the match is included in the specified match. + * @param mtc match to be compared + * @return result of check + */ + boolean in(final FTStringMatch mtc) { + return start >= mtc.start && end <= mtc.end; + } + + @Override + public boolean equals(final Object ftm) { + if(!(ftm instanceof FTStringMatch)) return false; + final FTStringMatch sm = (FTStringMatch) ftm; + return start == sm.start && end == sm.end; + } + + @Override + public int compareTo(final FTStringMatch sm) { + final int s = start - sm.start; + return s == 0 ? end - sm.end : s; + } + + @Override + public int hashCode() { + final int h = start + 1; + return (h << 5) - h + end; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder().append(pos); + sb.append(':').append(start).append('-').append(end); + return exclude ? "not(" + sb + ')' : sb.toString(); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/IndexInfo.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/IndexInfo.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/IndexInfo.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/IndexInfo.java 2015-07-14 10:54:40.000000000 +0000 @@ -57,7 +57,7 @@ /** * Checks if the specified expression can be rewritten for index access. - * @param ex expression (must be {@link Context} or {@link AxisPath}) + * @param ex expression (must be {@link ContextValue} or {@link AxisPath}) * @param ft full-text flag * @return location step or {@code null} */ @@ -66,7 +66,7 @@ // context reference: work with index step Step s = step; - if(!(ex instanceof Context)) { + if(!(ex instanceof ContextValue)) { // check if index can be applied if(!(ex instanceof AxisPath)) return false; // accept only single axis steps as first expression @@ -82,7 +82,7 @@ final boolean elem = s.test.type == NodeType.ELM; if(elem) { // only do check if database is up-to-date if no namespaces occur and if name test is used - if(!data.meta.uptodate || data.nspaces.size() != 0 || s.test.kind != Kind.NAME) return false; + if(!data.meta.uptodate || !data.nspaces.isEmpty() || s.test.kind != Kind.NAME) return false; test = (NameTest) s.test; final Stats stats = data.elemNames.stat(data.elemNames.id(test.name.local())); if(stats == null || !stats.isLeaf()) return false; @@ -119,7 +119,7 @@ */ private ParseExpr invert(final ParseExpr root) { // handle context node - if(orig instanceof Context) { + if(orig instanceof ContextValue) { // add attribute step if(!attr || step.test.name == null) return root; final Step as = Step.get(step.info, Axis.SELF, step.test); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/list/AnnList.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/list/AnnList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/list/AnnList.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/list/AnnList.java 2015-07-14 10:54:40.000000000 +0000 @@ -79,20 +79,22 @@ final AnnList tmp = new AnnList(); boolean pub = false, priv = false, up = false; for(final Ann ann : this) { - if(ann.sig == Annotation.PUBLIC) pub = true; - else if(ann.sig == Annotation.PRIVATE) priv = true; - else if(ann.sig == Annotation.UPDATING) up = true; + final Annotation sig = ann.sig; + if(sig == Annotation.PUBLIC) pub = true; + else if(sig == Annotation.PRIVATE) priv = true; + else if(sig == Annotation.UPDATING) up = true; tmp.add(ann); } for(final Ann ann : al) { - if(ann.sig == Annotation.PUBLIC) { + final Annotation sig = ann.sig; + if(sig == Annotation.PUBLIC) { if(pub) continue; if(priv) return null; - } else if(ann.sig == Annotation.PRIVATE) { + } else if(sig == Annotation.PRIVATE) { if(pub) return null; if(priv) continue; - } else if(ann.sig == Annotation.UPDATING && up) { + } else if(sig == Annotation.UPDATING && up) { continue; } tmp.add(ann); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/list/ANodeList.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/list/ANodeList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/list/ANodeList.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/list/ANodeList.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,15 +2,18 @@ import java.util.*; +import org.basex.data.*; +import org.basex.query.iter.*; import org.basex.query.value.*; import org.basex.query.value.node.*; -import org.basex.query.value.seq.*; import org.basex.query.value.type.*; import org.basex.util.*; import org.basex.util.list.*; /** - * This is a light-weight container for XML nodes. + * Resizable-array implementation for nodes. If the {@link #check} function is called, the stored + * nodes will be sorted and duplicates will before removed before they are returned as value or + * via an iterator. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen @@ -18,6 +21,10 @@ public final class ANodeList extends ElementList implements Iterable { /** Element container. */ private ANode[] list; + /** Sort flag. */ + private boolean sort; + /** Check incoming nodes for potential duplicates and unsorted entries. */ + private boolean check; /** * Constructor. @@ -28,70 +35,298 @@ /** * Constructor, specifying an initial array capacity. - * @param c array capacity + * @param capacity array capacity */ - public ANodeList(final int c) { - list = new ANode[c]; + public ANodeList(final int capacity) { + list = new ANode[capacity]; } /** - * Constructor, specifying initial nodes. - * @param n initial nodes + * Lightweight constructor, assigning the specified array. + * @param elements initial array */ - public ANodeList(final ANode... n) { - list = n; - size = n.length; + public ANodeList(final ANode... elements) { + list = elements; + size = elements.length; + } + + /** + * Checks all nodes for potential duplicates and orderedness. + * @return self reference + */ + public ANodeList check() { + check = true; + return this; } /** * Adds an element to the array. - * @param e element to be added + * @param element element to be added */ - public void add(final ANode e) { - if(size == list.length) copyOf(newSize()); - list[size++] = e; + public void add(final ANode element) { + ANode[] lst = list; + final int s = size; + if(s == lst.length) lst = copyOf(newSize()); + if(s != 0 && check && !sort) sort = lst[s - 1].diff(element) > 0; + lst[s] = element; + list = lst; + size = s + 1; } /** * Sets an element at the specified index position. - * @param i index - * @param e element to be set + * @param index index + * @param element element to be set */ - public void set(final int i, final ANode e) { - if(i >= list.length) copyOf(newSize(i + 1)); - list[i] = e; - size = Math.max(size, i + 1); + public void set(final int index, final ANode element) { + ANode[] lst = list; + if(index >= lst.length) lst = copyOf(newSize(index + 1)); + lst[index] = element; + list = lst; + size = Math.max(size, index + 1); } /** * Returns the specified element. - * @param p position - * @return value + * @param index index + * @return element + */ + public ANode get(final int index) { + return list[index]; + } + + /** + * Deletes the element at the specified position. + * @param index index of the element to delete + * @return deleted element */ - public ANode get(final int p) { - return list[p]; + public ANode delete(final int index) { + final ANode l = list[index]; + Array.move(list, index + 1, -1, --size - index); + return l; } /** - * Returns an array with all elements. + * Returns a {@link Value} representation of all items. * @return array */ public Value value() { - return Seq.get(list, size, NodeType.NOD); + sort(); + return ValueBuilder.value(list, size, NodeType.NOD); + } + + @Override + public Iterator iterator() { + sort(); + return new ArrayIterator<>(list, size); + } + + /** + * Returns an iterator over the items in this list. + * The list must not be modified after the iterator has been requested. + * @return the iterator + */ + public BasicNodeIter iter() { + sort(); + + return new BasicNodeIter() { + int pos; + + @Override + public Value value() { + return ANodeList.this.value(); + } + + @Override + public long size() { + return size; + } + + @Override + public ANode next() { + return pos < size ? list[pos++] : null; + } + + @Override + public ANode get(final long i) { + return list[(int) i]; + } + }; + } + + /** + * Sorts the nodes and checks if binary search can be applied to this iterator. + * This is the case if all nodes are {@link DBNode}s and refer to the same database. + * @return result of check + */ + public boolean dbnodes() { + sort(); + + final int s = size; + final ANode[] lst = list; + final Data data = s > 0 ? lst[0].data() : null; + if(data == null) return false; + for(int l = 1; l < s; ++l) if(data != lst[l].data()) return false; + return true; + } + + /** + * Checks if the iterator contains a database node with the specified pre value. + * @param node node to be found + * @param db indicates if all nodes are sorted {@link DBNode}s + * @return position, or {@code -1} + */ + public int indexOf(final ANode node, final boolean db) { + if(db) return node instanceof DBNode ? Math.max(binarySearch((DBNode) node, 0, size), -1) : -1; + + final long sz = size(); + final ANode[] lst = list; + for(int s = 0; s < sz; ++s) if(lst[s].is(node)) return s; + return -1; } /** - * Resizes the array. + * Performs a binary search on the given range of this sequence iterator, + * assuming that all nodes are {@link DBNode}s from the same {@link Data} + * instance (i.e., {@link #dbnodes()} returns {@code true}). + * @param node node to find + * @param start start of the search interval + * @param length length of the search interval + * @return position of the item or {@code -insertPosition - 1} if not found + */ + public int binarySearch(final DBNode node, final int start, final int length) { + if(size == 0 || node.data() != list[0].data()) return -start - 1; + int l = start, r = start + length - 1; + final ANode[] lst = list; + while(l <= r) { + final int m = l + r >>> 1, mpre = ((DBNode) lst[m]).pre(), npre = node.pre(); + if(mpre == npre) return m; + if(mpre < npre) l = m + 1; + else r = m - 1; + } + return -(l + 1); + } + + /** + * Creates a resized array. * @param s new size + * @return new array */ - private void copyOf(final int s) { + private ANode[] copyOf(final int s) { final ANode[] tmp = new ANode[s]; System.arraycopy(list, 0, tmp, 0, size); - list = tmp; + return tmp; } - @Override - public Iterator iterator() { - return new ArrayIterator<>(list, size); + /** + * Sorts the nodes. + */ + private void sort() { + if(!check) return; + + final int s = size; + if(s > 1) { + if(sort) sort(0, s); + + // remove duplicates + int i = 1; + final ANode[] lst = list; + for(int j = 1; j < s; ++j) { + while(j < s && lst[i - 1].is(lst[j])) j++; + if(j < s) lst[i++] = lst[j]; + } + size = i; + } + check = false; + } + + /** + * Recursively sorts the specified items via QuickSort + * (derived from Java's sort algorithms). + * @param s start position + * @param e end position + */ + private void sort(final int s, final int e) { + if(e < 7) { + for(int i = s; i < e + s; ++i) { + for(int j = i; j > s && list[j - 1].diff(list[j]) > 0; j--) s(j, j - 1); + } + return; + } + + int m = s + (e >> 1); + if(e > 7) { + int l = s; + int n = s + e - 1; + if(e > 40) { + final int k = e >>> 3; + l = m(l, l + k, l + (k << 1)); + m = m(m - k, m, m + k); + n = m(n - (k << 1), n - k, n); + } + m = m(l, m, n); + } + final ANode v = list[m]; + + int a = s, b = a, c = s + e - 1, d = c; + while(true) { + while(b <= c) { + final int h = list[b].diff(v); + if(h > 0) break; + if(h == 0) s(a++, b); + ++b; + } + while(c >= b) { + final int h = list[c].diff(v); + if(h < 0) break; + if(h == 0) s(c, d--); + --c; + } + if(b > c) break; + s(b++, c--); + } + + final int n = s + e; + int k = Math.min(a - s, b - a); + s(s, b - k, k); + k = Math.min(d - c, n - d - 1); + s(b, n - k, k); + + if((k = b - a) > 1) sort(s, k); + if((k = d - c) > 1) sort(n - k, k); + } + + /** + * Swaps x[a .. (a+n-1)] with x[b .. (b+n-1)]. + * @param a first offset + * @param b second offset + * @param n number of values + */ + private void s(final int a, final int b, final int n) { + for(int i = 0; i < n; ++i) s(a + i, b + i); + } + + /** + * Returns the index of the median of the three indexed integers. + * @param a first offset + * @param b second offset + * @param c thirst offset + * @return median + */ + private int m(final int a, final int b, final int c) { + return list[a].diff(list[b]) < 0 ? + list[b].diff(list[c]) < 0 ? b : list[a].diff(list[c]) < 0 ? c : a : + list[b].diff(list[c]) > 0 ? b : list[a].diff(list[c]) > 0 ? c : a; + } + + /** + * Swaps two entries. + * @param a first position + * @param b second position + */ + private void s(final int a, final int b) { + final ANode tmp = list[a]; + list[a] = list[b]; + list[b] = tmp; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/list/ExprList.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/list/ExprList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/list/ExprList.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/list/ExprList.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,7 +4,7 @@ import org.basex.util.list.*; /** - * This is a simple container for expressions. + * Resizable-array implementation for XQuery expressions. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/list/ItemList.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/list/ItemList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/list/ItemList.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/list/ItemList.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,185 @@ +package org.basex.query.util.list; + +import java.util.*; + +import org.basex.query.iter.*; +import org.basex.query.value.*; +import org.basex.query.value.item.*; +import org.basex.query.value.type.*; +import org.basex.util.*; +import org.basex.util.list.*; + +/** + * Resizable-array implementation for items. + * + * @author BaseX Team 2005-15, BSD License + * @author Leo Woerteler + */ +public final class ItemList extends ElementList implements Iterable { + /** Element container. */ + private Item[] list; + + /** + * Default constructor. + */ + public ItemList() { + this(1); + } + + /** + * Constructor, specifying an initial array capacity. + * @param capacity array capacity + */ + public ItemList(final int capacity) { + list = new Item[capacity]; + } + + /** + * Constructor, specifying an initial entry. + * @param element array capacity + */ + public ItemList(final Item element) { + list = new Item[] { element }; + size = 1; + } + + /** + * Returns the specified element. + * @param p position + * @return value + */ + public Item get(final int p) { + return list[p]; + } + + /** + * Adds an element to the array. + * @param element element to be added + * @return self reference + */ + public ItemList add(final Item element) { + if(size == list.length) resize(newSize()); + list[size++] = element; + return this; + } + + /** + * Adds all elements in the given value to this list. + * @param value value to add + * @return self reference + */ + public ItemList add(final Value value) { + if(value instanceof Item) return add((Item) value); + final long n = value.size(); + if(n > Integer.MAX_VALUE - size) throw Util.notExpected(n); + final int newSize = size + (int) n; + int sz = size; + if(newSize > sz) { + do { + sz = Array.newSize(sz, factor); + } while(sz < newSize); + resize(sz); + } + size += value.writeTo(list, size); + return this; + } + + /** + * Resizes the array. + * @param sz new size + */ + private void resize(final int sz) { + final Item[] tmp = new Item[sz]; + System.arraycopy(list, 0, tmp, 0, size); + list = tmp; + } + + /** + * Returns an array with all elements and invalidates the internal array. + * Warning: the function must only be called if the list is discarded afterwards. + * @return array (internal representation!) + */ + public Item[] finish() { + Item[] lst = list; + final int s = size; + if(s != lst.length) { + lst = new Item[s]; + System.arraycopy(list, 0, lst, 0, s); + } + list = null; + return lst; + } + + /** + * Returns a value containing the items in this list. + * @return the value + */ + public Value value() { + return value(null); + } + + /** + * Returns a value with the given element type containing the items in this list. + * @param type item type (not checked), may be {@code null} + * @return the value + */ + public Value value(final Type type) { + return ValueBuilder.value(list, size, type); + } + + /** + * Sets the item at the given position to the given value. + * @param pos position + * @param item new value + * @return self reference + */ + public ItemList set(final int pos, final Item item) { + if(pos >= size) resize(newSize(pos + 1)); + list[pos] = item; + return this; + } + + /** + * Returns an iterator over the items in this list. + * The list must not be modified after the iterator has been requested. + * @return the iterator + */ + public Iter iter() { + return new Iter() { + int pos; + + @Override + public Value value() { + return ItemList.this.value(); + } + + @Override + public long size() { + return size; + } + + @Override + public Item next() { + return pos < size ? list[pos++] : null; + } + + @Override + public Item get(final long i) { + return list[(int) i]; + } + }; + } + + /** + * Returns the current backing array of this list. + * @return the backing array + */ + public Item[] internal() { + return list; + } + + @Override + public Iterator iterator() { + return new ArrayIterator<>(list, size); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/list/ValueList.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/list/ValueList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/list/ValueList.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/list/ValueList.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,7 +5,7 @@ import org.basex.util.list.*; /** - * This is a simple container for values. + * Resizable-array implementation for values. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/pkg/ModuleLoader.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/pkg/ModuleLoader.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/pkg/ModuleLoader.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/pkg/ModuleLoader.java 2015-07-14 10:54:40.000000000 +0000 @@ -253,8 +253,15 @@ final Package pkg = new PkgParser(ii).parse(pkgDesc); // check if package contains a jar descriptor final IOFile jarDesc = new IOFile(pkgDir, PkgText.JARDESC); + // choose module directory (support for both 2010 and 2012 specs) + IOFile modDir = new IOFile(pkgDir, PkgText.CONTENT); + if(!modDir.exists()) modDir = new IOFile(pkgDir, string(pkg.abbrev)); + // add jars to classpath - if(jarDesc.exists()) addJar(jarDesc, pkgDir, string(pkg.abbrev), ii); + if(jarDesc.exists()) { + final JarDesc desc = new JarParser(ii).parse(jarDesc); + for(final byte[] u : desc.jars) addURL(new IOFile(modDir, string(u))); + } // package has dependencies -> they have to be loaded first => put package // in list with packages to be loaded @@ -269,7 +276,7 @@ } } for(final Component comp : pkg.comps) { - final String p = new IOFile(new IOFile(pkgDir, string(pkg.abbrev)), string(comp.file)).path(); + final String p = new IOFile(modDir, string(comp.file)).path(); qp.module(token(p), comp.uri); } if(toLoad.contains(name)) toLoad.delete(name); @@ -277,22 +284,6 @@ } /** - * Adds the jar files registered in jarDesc. - * @param jarDesc jar descriptor - * @param pkgDir package directory - * @param modDir module directory - * @param ii input info - * @throws QueryException query exception - */ - private void addJar(final IOFile jarDesc, final IOFile pkgDir, final String modDir, - final InputInfo ii) throws QueryException { - - // add new URLs - final JarDesc desc = new JarParser(ii).parse(jarDesc); - for(final byte[] u : desc.jars) addURL(new IOFile(new IOFile(pkgDir, modDir), string(u))); - } - - /** * Adds a URL to the cache. * @param jar jar file to be added */ diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/pkg/PkgParser.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/pkg/PkgParser.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/pkg/PkgParser.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/pkg/PkgParser.java 2015-07-14 10:54:40.000000000 +0000 @@ -66,7 +66,7 @@ private void parseAttributes(final ANode node, final Package p, final byte[] root) throws QueryException { - final AxisIter atts = node.attributes(); + final BasicNodeIter atts = node.attributes(); for(ANode next; (next = atts.next()) != null;) { final byte[] name = next.name(); if(eq(A_NAME, name)) p.name = next.string(); @@ -94,7 +94,7 @@ * @throws QueryException query exception */ private void parseChildren(final ANode node, final Package p) throws QueryException { - final AxisIter ch = childElements(node); + final BasicNodeIter ch = childElements(node); for(ANode next; (next = ch.next()) != null;) { final QNm name = next.qname(); if(eqNS(DEPEND, name)) p.dep.add(parseDependency(next)); @@ -109,7 +109,7 @@ * @throws QueryException query exception */ private Dependency parseDependency(final ANode node) throws QueryException { - final AxisIter atts = node.attributes(); + final BasicNodeIter atts = node.attributes(); final Dependency d = new Dependency(); for(ANode next; (next = atts.next()) != null;) { final byte[] name = next.name(); @@ -131,7 +131,7 @@ * @throws QueryException query exception */ private Component parseComp(final ANode node) throws QueryException { - final AxisIter ch = childElements(node); + final BasicNodeIter ch = childElements(node); final Component c = new Component(); for(ANode next; (next = ch.next()) != null;) { final QNm name = next.qname(); @@ -152,10 +152,10 @@ * @param node root node * @return child element iterator */ - private static AxisIter childElements(final ANode node) { - return new AxisIter() { + private static BasicNodeIter childElements(final ANode node) { + return new BasicNodeIter() { /** Child iterator. */ - final AxisIter ch = node.children(); + final BasicNodeIter ch = node.children(); @Override public ANode next() { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/pkg/PkgText.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/pkg/PkgText.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/pkg/PkgText.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/pkg/PkgText.java 2015-07-14 10:54:40.000000000 +0000 @@ -24,6 +24,8 @@ String EXPATH = "EXPath"; /** Internal type. */ String INTERNAL = "Internal"; + /** Content directory. */ + String CONTENT = "content"; /** root element. */ /** Element package. */ diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/regex/parse/RegExParser.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/regex/parse/RegExParser.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/regex/parse/RegExParser.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/regex/parse/RegExParser.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,8 +7,8 @@ import org.basex.query.*; import org.basex.query.util.regex.*; +import org.basex.query.util.*; import org.basex.util.*; - import static org.basex.util.Token.*; import static java.util.regex.Pattern.*; @@ -599,31 +599,31 @@ throw new Error("Missing return statement in function"); } - private boolean jj_2_1(final int xla) { + private boolean jj_2_1(int xla) { jj_la = xla; jj_lastpos = jj_scanpos = token; try { return !jj_3_1(); } - catch(final LookaheadSuccess ls) { return true; } + catch(LookaheadSuccess ls) { return true; } finally { jj_save(0, xla); } } - private boolean jj_2_2(final int xla) { + private boolean jj_2_2(int xla) { jj_la = xla; jj_lastpos = jj_scanpos = token; try { return !jj_3_2(); } - catch(final LookaheadSuccess ls) { return true; } + catch(LookaheadSuccess ls) { return true; } finally { jj_save(1, xla); } } - private boolean jj_2_3(final int xla) { + private boolean jj_2_3(int xla) { jj_la = xla; jj_lastpos = jj_scanpos = token; try { return !jj_3_3(); } - catch(final LookaheadSuccess ls) { return true; } + catch(LookaheadSuccess ls) { return true; } finally { jj_save(2, xla); } } - private boolean jj_2_4(final int xla) { + private boolean jj_2_4(int xla) { jj_la = xla; jj_lastpos = jj_scanpos = token; try { return !jj_3_4(); } - catch(final LookaheadSuccess ls) { return true; } + catch(LookaheadSuccess ls) { return true; } finally { jj_save(3, xla); } } @@ -757,7 +757,7 @@ /** Constructor with user supplied Token Manager. */ - public RegExParser(final TokenManager tm) { + public RegExParser(TokenManager tm) { token_source = tm; token = new Token(); jj_ntk = -1; @@ -767,7 +767,7 @@ } /** Reinitialise. */ - public void ReInit(final TokenManager tm) { + public void ReInit(TokenManager tm) { token_source = tm; token = new Token(); jj_ntk = -1; @@ -776,7 +776,7 @@ for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); } - private Token jj_consume_token(final int kind) throws ParseException { + private Token jj_consume_token(int kind) throws ParseException { Token oldToken; if ((oldToken = token).next != null) token = token.next; else token = token.next = token_source.getNextToken(); @@ -785,8 +785,8 @@ jj_gen++; if (++jj_gc > 100) { jj_gc = 0; - for(final JJCalls jj_2_rtn : jj_2_rtns) { - JJCalls c = jj_2_rtn; + for (int i = 0; i < jj_2_rtns.length; i++) { + JJCalls c = jj_2_rtns[i]; while (c != null) { if (c.gen < jj_gen) c.first = null; c = c.next; @@ -802,7 +802,7 @@ static private final class LookaheadSuccess extends java.lang.Error { } final private LookaheadSuccess jj_ls = new LookaheadSuccess(); - private boolean jj_scan_token(final int kind) { + private boolean jj_scan_token(int kind) { if (jj_scanpos == jj_lastpos) { jj_la--; if (jj_scanpos.next == null) { @@ -834,7 +834,7 @@ } /** Get the specific Token. */ - final public Token getToken(final int index) { + final public Token getToken(int index) { Token t = jj_lookingAhead ? jj_scanpos : token; for (int i = 0; i < index; i++) { if (t.next != null) t = t.next; @@ -850,13 +850,13 @@ return (jj_ntk = jj_nt.kind); } - private final java.util.List jj_expentries = new java.util.ArrayList(); + private java.util.List jj_expentries = new java.util.ArrayList(); private int[] jj_expentry; private int jj_kind = -1; - private final int[] jj_lasttokens = new int[100]; + private int[] jj_lasttokens = new int[100]; private int jj_endpos; - private void jj_add_error_token(final int kind, final int pos) { + private void jj_add_error_token(int kind, int pos) { if (pos >= 100) return; if (pos == jj_endpos + 1) { jj_lasttokens[jj_endpos++] = kind; @@ -865,8 +865,8 @@ for (int i = 0; i < jj_endpos; i++) { jj_expentry[i] = jj_lasttokens[i]; } - jj_entries_loop: for(final Object name : jj_expentries) { - final int[] oldentry = (int[])(name); + jj_entries_loop: for (java.util.Iterator it = jj_expentries.iterator(); it.hasNext();) { + int[] oldentry = (int[])(it.next()); if (oldentry.length == jj_expentry.length) { for (int i = 0; i < jj_expentry.length; i++) { if (oldentry[i] != jj_expentry[i]) { @@ -884,7 +884,7 @@ /** Generate ParseException. */ public ParseException generateParseException() { jj_expentries.clear(); - final boolean[] la1tokens = new boolean[25]; + boolean[] la1tokens = new boolean[25]; if (jj_kind >= 0) { la1tokens[jj_kind] = true; jj_kind = -1; @@ -908,7 +908,7 @@ jj_endpos = 0; jj_rescan_token(); jj_add_error_token(0, 0); - final int[][] exptokseq = new int[jj_expentries.size()][]; + int[][] exptokseq = new int[jj_expentries.size()][]; for (int i = 0; i < jj_expentries.size(); i++) { exptokseq[i] = jj_expentries.get(i); } @@ -940,12 +940,12 @@ } p = p.next; } while (p != null); - } catch(final LookaheadSuccess ls) { } + } catch(LookaheadSuccess ls) { } } jj_rescan = false; } - private void jj_save(final int index, final int xla) { + private void jj_save(int index, int xla) { JJCalls p = jj_2_rtns[index]; while (p.gen > jj_gen) { if (p.next == null) { p = p.next = new JJCalls(); break; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/regex/RegExpList.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/regex/RegExpList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/regex/RegExpList.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/regex/RegExpList.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,7 +3,7 @@ import org.basex.util.list.*; /** - * This is a simple container for expressions. + * Resizable-array implementation for regular expressions. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/util/UriResolver.java basex-8.2.3/basex-core/src/main/java/org/basex/query/util/UriResolver.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/util/UriResolver.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/util/UriResolver.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,18 @@ +package org.basex.query.util; + +import org.basex.io.*; +import org.basex.query.value.item.*; + +/** + * Interface for resolving URIs in query modules. + */ +public interface UriResolver { + /** + * Locates a file, given the optional namespace URI and a path to the location. + * @param path path (relative or absolute) + * @param uri uri (can be {@code null}) + * @param base base uri (can be {@code null}) + * @return reference to resources + */ + IO resolve(String path, String uri, Uri base); +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/ArrayBuilder.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/ArrayBuilder.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/ArrayBuilder.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/ArrayBuilder.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,5 @@ package org.basex.query.value.array; -import java.util.*; - import org.basex.query.util.fingertree.*; import org.basex.query.value.*; @@ -109,8 +107,7 @@ */ public ArrayBuilder append(final Array arr) { if(!(arr instanceof BigArray)) { - final Iterator members = arr.members(); - while(members.hasNext()) append(members.next()); + for(final Value val : arr.members()) append(val); return this; } @@ -195,18 +192,13 @@ sb.append(vals[first]); for(int i = 1; i < n; i++) sb.append(", ").append(vals[(first + i) % CAP]); } - return sb.append(']').toString(); - } - - if(inLeft > 0) { + } else { final int first = (mid - inLeft + CAP) % CAP; sb.append(vals[first]); for(int i = 1; i < inLeft; i++) sb.append(", ").append(vals[(first + i) % CAP]); - sb.append(", "); + for(final Value val : tree) sb.append(", ").append(val); + for(int i = 0; i < inRight; i++) sb.append(", ").append(vals[(mid + i) % CAP]); } - - tree.toString(sb); - for(int i = 0; i < inRight; i++) sb.append(", ").append(vals[(mid + i) % CAP]); return sb.append(']').toString(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/Array.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/Array.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/Array.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/Array.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,7 +8,6 @@ import org.basex.query.*; import org.basex.query.expr.*; import org.basex.query.func.fn.*; -import org.basex.query.iter.*; import org.basex.query.util.collation.*; import org.basex.query.util.list.*; import org.basex.query.value.*; @@ -64,35 +63,13 @@ /** * Creates an array containing the given elements. - * @param elems elements + * @param values elements * @return the resulting array */ @SafeVarargs - public static Array from(final Value... elems) { + public static Array from(final Value... values) { final ArrayBuilder builder = new ArrayBuilder(); - for(final Value val : elems) builder.append(val); - return builder.freeze(); - } - - /** - * Creates an array containing the elements from the given {@link Iterable}. - * @param iter the iterable - * @return the resulting array - */ - public static Array from(final Iterable iter) { - final ArrayBuilder builder = new ArrayBuilder(); - for(final Value val : iter) builder.append(val); - return builder.freeze(); - } - - /** - * Creates an array containing the elements from the given {@link Iterator}. - * @param iter the iterator - * @return the resulting array - */ - public static Array from(final Iterator iter) { - final ArrayBuilder builder = new ArrayBuilder(); - while(iter.hasNext()) builder.append(iter.next()); + for(final Value val : values) builder.append(val); return builder.freeze(); } @@ -183,7 +160,7 @@ * Running time: O(n) * @return reversed version of this array */ - public abstract Array reverse(); + public abstract Array reverseArray(); @Override public final boolean isEmpty() { @@ -218,17 +195,29 @@ /** * Iterator over the members of this array. - * @param reverse flag for iterating from back to front + * @param start starting position + * (i.e. the position initially returned by {@link ListIterator#nextIndex()}) * @return array over the array members */ - public abstract ListIterator members(final boolean reverse); + public abstract ListIterator iterator(final long start); + + /** Iterable over the elements of this array. */ + private Iterable iterable; /** * Iterator over the members of this array. * @return array over the array members */ - public final ListIterator members() { - return members(false); + public final Iterable members() { + if(iterable == null) { + iterable = new Iterable() { + @Override + public Iterator iterator() { + return Array.this.iterator(0); + } + }; + } + return iterable; } /** @@ -277,12 +266,6 @@ */ abstract void checkInvariants(); - /** - * Returns an array containing the number of elements stored at each level of the tree. - * @return array of sizes - */ - abstract long[] sizes(); - @Override public Value invValue(final QueryContext qc, final InputInfo ii, final Value... args) throws QueryException { @@ -347,8 +330,7 @@ @Override public Array materialize(final InputInfo ii) throws QueryException { final ArrayBuilder builder = new ArrayBuilder(); - final Iterator iter = members(); - while(iter.hasNext()) builder.append(iter.next().materialize(ii)); + for(final Value val : members()) builder.append(val.materialize(ii)); return builder.freeze(); } @@ -366,11 +348,8 @@ @Override public long atomSize() { long s = 0; - final Iterator iter = members(); - while(iter.hasNext()) { - final Value v = iter.next(); - final long vs = v.size(); - for(int i = 0; i < vs; i++) s += v.itemAt(i).atomSize(); + for(final Value val : members()) { + for(final Item it : val) s += it.atomSize(); } return s; } @@ -386,8 +365,8 @@ final long s = atomSize(), size = arraySize(); if(single && s > 1) throw SEQFOUND_X.get(ii, this); if(size == 1) return get(0).atomValue(ii); - final ValueBuilder vb = new ValueBuilder((int) s); - for(long a = 0; a < size; a++) vb.add(get(a).atomValue(ii)); + final ValueBuilder vb = new ValueBuilder(); + for(final Value val : members()) vb.add(val.atomValue(ii)); return vb.value(); } @@ -412,16 +391,14 @@ public void string(final TokenBuilder tb, final InputInfo ii) throws QueryException { tb.add('['); int c = 0; - final Iterator iter = members(); - while(iter.hasNext()) { + for(final Value val : members()) { if(c++ > 0) tb.add(", "); - final Value v = iter.next(); - final long vs = v.size(); + final long vs = val.size(); if(vs != 1) tb.add('('); int cc = 0; for(int i = 0; i < vs; i++) { if(cc++ > 0) tb.add(", "); - final Item it = v.itemAt(i); + final Item it = val.itemAt(i); if(it instanceof Array) ((Array) it).string(tb, ii); else if(it instanceof Map) ((Map) it).string(tb, 0, ii); else tb.add(it.toString()); @@ -438,8 +415,7 @@ */ public boolean hasType(final ArrayType t) { if(!t.retType.eq(SeqType.ITEM_ZM)) { - final Iterator iter = members(); - while(iter.hasNext()) if(!t.retType.instance(iter.next())) return false; + for(final Value val : members()) if(!t.retType.instance(val)) return false; } return true; } @@ -458,7 +434,7 @@ if(item instanceof Array) { final Array o = (Array) item; if(arraySize() != o.arraySize()) return false; - final Iterator it1 = members(), it2 = o.members(); + final Iterator it1 = iterator(0), it2 = o.iterator(0); while(it1.hasNext()) { final Value v1 = it1.next(), v2 = it2.next(); if(v1.size() != v2.size() || !new Compare(ii).collation(coll).equal(v1, v2)) @@ -487,7 +463,7 @@ public Object[] toJava() throws QueryException { final long size = arraySize(); final Object[] tmp = new Object[(int) size]; - final Iterator iter = members(); + final Iterator iter = iterator(0); for(int i = 0; iter.hasNext(); i++) tmp[i] = iter.next().toJava(); return tmp; } @@ -495,7 +471,7 @@ @Override public String toString() { final StringBuilder tb = new StringBuilder().append('['); - final Iterator iter = members(); + final Iterator iter = iterator(0); for(boolean fst = true; iter.hasNext(); fst = false) { if(!fst) tb.append(", "); final Value value = iter.next(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/BigArray.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/BigArray.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/BigArray.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/BigArray.java 2015-07-14 10:54:40.000000000 +0000 @@ -199,7 +199,7 @@ midNodes[i] = new LeafNode(arr); } - return new BigArray(left, middle.concat(midNodes, other.middle), other.right); + return new BigArray(left, middle.concat(midNodes, n, other.middle), other.right); } @Override @@ -220,7 +220,7 @@ } @Override - public Array reverse() { + public Array reverseArray() { final int l = left.length, r = right.length; final Value[] newLeft = new Value[r], newRight = new Value[l]; for(int i = 0; i < r; i++) newLeft[i] = right[r - 1 - i]; @@ -534,17 +534,29 @@ } @Override - public ListIterator members(final boolean reverse) { + public ListIterator iterator(final long start) { final Value[] ls = left, rs = right; - final int l = ls.length, m = (int) middle.size(), r = rs.length; - final ListIterator sub = middle.listIterator(reverse); + final int l = ls.length , r = rs.length, startPos; + final long m = middle.size(); + final ListIterator sub; + if(start < l) { + startPos = (int) start - l; + sub = middle.listIterator(0); + } else if(start - l < m) { + startPos = 0; + sub = middle.listIterator(start - l); + } else { + startPos = (int) (start - l - m) + 1; + sub = middle.listIterator(m); + } + return new ListIterator() { - int pos = reverse ? r + 1 : -l; + int pos = startPos; @Override public int nextIndex() { return pos < 0 ? l + pos - : pos > 0 ? l + m + pos - 1 + : pos > 0 ? (int) (l + m + pos - 1) : l + sub.nextIndex(); } @@ -574,7 +586,7 @@ @Override public int previousIndex() { return pos < 0 ? l + pos - 1 - : pos > 0 ? l + m + pos - 2 + : pos > 0 ? (int) (l + m + pos - 2) : l + sub.previousIndex(); } @@ -628,13 +640,6 @@ } @Override - long[] sizes() { - final long[] sizes = middle.sizes(1); - sizes[0] = left.length + right.length; - return sizes; - } - - @Override Array consSmall(final Value[] vals) { final int a = vals.length, b = left.length, n = a + b; if(n <= MAX_DIGIT) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/EmptyArray.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/EmptyArray.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/EmptyArray.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/EmptyArray.java 2015-07-14 10:54:40.000000000 +0000 @@ -78,7 +78,7 @@ } @Override - public Array reverse() { + public Array reverseArray() { return this; } @@ -96,7 +96,7 @@ } @Override - public ListIterator members(final boolean reverse) { + public ListIterator iterator(final long size) { return Collections.emptyListIterator(); } @@ -106,11 +106,6 @@ } @Override - long[] sizes() { - return new long[] { 0 }; - } - - @Override Array consSmall(final Value[] vals) { return new SmallArray(vals); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/LeafNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/LeafNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/LeafNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/LeafNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,7 +11,7 @@ * @author BaseX Team 2005-15, BSD License * @author Leo Woerteler */ -final class LeafNode extends Node { +final class LeafNode implements Node { /** Elements stored in this leaf node. */ final Value[] values; @@ -25,12 +25,12 @@ } @Override - protected long size() { + public long size() { return values.length; } @Override - protected LeafNode reverse() { + public LeafNode reverse() { final int n = values.length; final Value[] out = new Value[n]; for(int i = 0; i < n; i++) out[i] = values[n - 1 - i]; @@ -38,7 +38,7 @@ } @Override - protected boolean insert(final Node[] siblings, + public boolean insert(final Node[] siblings, final long pos, final Value val) { final int p = (int) pos, n = values.length; final Value[] vals = new Value[n + 1]; @@ -93,20 +93,11 @@ } @Override - protected NodeLike remove(final long pos) { - final int p = (int) pos, n = values.length; - final Value[] out = new Value[n - 1]; - System.arraycopy(values, 0, out, 0, p); - System.arraycopy(values, p + 1, out, p, n - 1 - p); - return n == Array.MIN_LEAF ? new PartialLeafNode(out) : new LeafNode(out); - } - - @Override - protected Node[] remove(final Node left, + public NodeLike[] remove(final Node left, final Node right, final long pos) { final int p = (int) pos, n = values.length; @SuppressWarnings("unchecked") - final Node[] out = new Node[] { left, null, right }; + final NodeLike[] out = new NodeLike[] { left, null, right }; if(n > Array.MIN_LEAF) { // we do not have to split final Value[] vals = new Value[n - 1]; @@ -160,7 +151,10 @@ System.arraycopy(values, p + 1, vals, l + p, r - 1 - p); out[0] = new LeafNode(vals); out[1] = null; - } else { + return out; + } + + if(right != null) { // merge with right neighbor final Value[] rvals = ((LeafNode) right).values; final int l = values.length, r = rvals.length; @@ -170,44 +164,19 @@ System.arraycopy(rvals, 0, vals, l - 1, r); out[1] = null; out[2] = new LeafNode(vals); + return out; } + // underflow + final Value[] vals = new Value[n - 1]; + System.arraycopy(values, 0, vals, 0, p); + System.arraycopy(values, p + 1, vals, p, n - 1 - p); + out[1] = new PartialLeafNode(vals); return out; } @Override - protected NodeLike[] concat(final NodeLike other) { - @SuppressWarnings("unchecked") - final NodeLike[] arr = new NodeLike[2]; - - if(other instanceof PartialLeafNode) { - final Value[] left = values, right = ((PartialLeafNode) other).elems; - final int l = left.length, r = right.length, n = l + r; - if(n <= Array.MAX_LEAF) { - // merge into one node - final Value[] vals = new Value[n]; - System.arraycopy(left, 0, vals, 0, l); - System.arraycopy(right, 0, vals, l, r); - arr[0] = new LeafNode(vals); - } else { - // split into two - final int ll = n / 2, rl = n - ll, move = l - ll; - final Value[] newLeft = new Value[ll], newRight = new Value[rl]; - System.arraycopy(left, 0, newLeft, 0, ll); - System.arraycopy(left, ll, newRight, 0, move); - System.arraycopy(right, 0, newRight, move, r); - arr[0] = new LeafNode(newLeft); - arr[1] = new LeafNode(newRight); - } - } else { - arr[0] = this; - arr[1] = other; - } - return arr; - } - - @Override - protected int append(final NodeLike[] nodes, final int pos) { + public int append(final NodeLike[] nodes, final int pos) { if(pos == 0) { nodes[pos] = this; return 1; @@ -242,7 +211,7 @@ } @Override - protected NodeLike slice(final long off, final long size) { + public NodeLike slice(final long off, final long size) { final int p = (int) off, n = (int) size; final Value[] out = new Value[n]; System.arraycopy(values, p, out, 0, n); @@ -250,41 +219,24 @@ } @Override - protected Node replaceFirst(final Value newFirst) { - if(newFirst == values[0]) return this; - final Value[] vals = values.clone(); - vals[0] = newFirst; - return new LeafNode(vals); - } - - @Override - protected Node replaceLast(final Value newLast) { - if(newLast == values[values.length - 1]) return this; - final Value[] vals = values.clone(); - vals[vals.length - 1] = newLast; - return new LeafNode(vals); - } - - @Override - protected void toString(final StringBuilder sb, final int indent) { - for(int i = 0; i < indent; i++) sb.append(" "); - sb.append("Node(").append(size()).append(")").append(Arrays.toString(values)); - } - - @Override - protected long checkInvariants() { + public long checkInvariants() { if(values.length < Array.MIN_LEAF || values.length > Array.MAX_LEAF) throw new AssertionError("Wrong " + getClass().getSimpleName() + " size: " + values.length); return values.length; } @Override - protected int arity() { + public int arity() { return values.length; } @Override - protected Value getSub(final int index) { + public Value getSub(final int index) { return values[index]; } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + size() + ")" + Arrays.toString(values); + } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/PartialLeafNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/PartialLeafNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/PartialLeafNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/PartialLeafNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,7 +11,7 @@ * @author BaseX Team 2005-15, BSD License * @author Leo Woerteler */ -final class PartialLeafNode extends PartialNode { +final class PartialLeafNode implements NodeLike { /** The single element. */ final Value[] elems; @@ -24,41 +24,7 @@ } @Override - protected NodeLike[] concat(final NodeLike other) { - @SuppressWarnings("unchecked") - final NodeLike[] out = new NodeLike[2]; - if(other instanceof LeafNode) { - final Value[] ls = elems, rs = ((LeafNode) other).values; - final int l = ls.length, r = rs.length, n = l + r; - if(n <= Array.MAX_LEAF) { - // merge into one node - final Value[] vals = new Value[n]; - System.arraycopy(ls, 0, vals, 0, l); - System.arraycopy(rs, 0, vals, l, r); - out[0] = new LeafNode(vals); - } else { - // split into two - final int ll = n / 2, rl = n - ll, move = r - rl; - final Value[] newLeft = new Value[ll], newRight = new Value[rl]; - System.arraycopy(ls, 0, newLeft, 0, l); - System.arraycopy(rs, 0, newLeft, l, move); - System.arraycopy(rs, move, newRight, 0, rl); - out[0] = new LeafNode(newLeft); - out[1] = new LeafNode(newRight); - } - } else { - final Value[] elems2 = ((PartialLeafNode) other).elems; - final int l = elems.length, r = elems2.length, n = l + r; - final Value[] vals = new Value[n]; - System.arraycopy(elems, 0, vals, 0, l); - System.arraycopy(elems2, 0, vals, l, r); - out[0] = n < Array.MIN_LEAF ? new PartialLeafNode(vals) : new LeafNode(vals); - } - return out; - } - - @Override - protected int append(final NodeLike[] nodes, final int pos) { + public int append(final NodeLike[] nodes, final int pos) { if(pos == 0) { nodes[0] = this; return 1; @@ -96,8 +62,7 @@ } @Override - protected void toString(final StringBuilder sb, final int indent) { - for(int i = 0; i < indent; i++) sb.append(" "); - sb.append(getClass().getSimpleName()).append(Arrays.toString(elems)); + public String toString() { + return getClass().getSimpleName() + "(" + elems.length + ")" + Arrays.toString(elems); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/SmallArray.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/SmallArray.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/array/SmallArray.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/array/SmallArray.java 2015-07-14 10:54:40.000000000 +0000 @@ -100,7 +100,7 @@ } @Override - public Array reverse() { + public Array reverseArray() { final int n = elems.length; if(n == 1) return this; final Value[] es = new Value[n]; @@ -149,9 +149,9 @@ } @Override - public ListIterator members(final boolean reverse) { + public ListIterator iterator(final long start) { return new ListIterator() { - private int index = reverse ? elems.length : 0; + private int index = (int) Math.max(0, Math.min(start, elems.length)); @Override public int nextIndex() { @@ -210,11 +210,6 @@ } @Override - long[] sizes() { - return new long[] { elems.length }; - } - - @Override Array consSmall(final Value[] left) { final int l = left.length, r = elems.length, n = l + r; if(Math.min(l, r) >= MIN_DIGIT) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/item/Dtm.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/item/Dtm.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/item/Dtm.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/item/Dtm.java 2015-07-14 10:54:40.000000000 +0000 @@ -72,7 +72,7 @@ * @throws QueryException query exception */ public Dtm(final long ms, final InputInfo ii) throws QueryException { - this(Token.token(DateTime.format(new Date(ms), DateTime.FULL)), ii); + this(Token.token(DateTime.format(new Date(ms))), ii); } /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/item/FuncItem.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/item/FuncItem.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/item/FuncItem.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/item/FuncItem.java 2015-07-14 10:54:40.000000000 +0000 @@ -157,7 +157,7 @@ public FItem coerceTo(final FuncType ft, final QueryContext qc, final InputInfo ii, final boolean opt) throws QueryException { - if(params.length != ft.argTypes.length) throw castError(ii, this, ft); + if(params.length != ft.argTypes.length) throw INVPROMOTE_X_X_X.get(ii, seqType(), ft, this); final FuncType tp = funcType(); if(tp.instanceOf(ft)) return this; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/item/Item.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/item/Item.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/item/Item.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/item/Item.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,6 +11,7 @@ import org.basex.query.util.collation.*; import org.basex.query.value.*; import org.basex.query.value.node.*; +import org.basex.query.value.seq.*; import org.basex.query.value.type.*; import org.basex.query.value.type.Type.ID; import org.basex.util.*; @@ -61,6 +62,11 @@ } @Override + public final Item reverse() { + return this; + } + + @Override public final Item ebv(final QueryContext qc, final InputInfo ii) { return this; } @@ -189,6 +195,11 @@ return new ArrayInput(string(ii)); } + @Override + public Value subSeq(final long start, final long len) { + return len == 0 ? Empty.SEQ : this; + } + // Overridden by B64Stream, StrStream, Jav and Array. @Override public Item materialize(final InputInfo ii) throws QueryException { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/item/QNm.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/item/QNm.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/item/QNm.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/item/QNm.java 2015-07-14 10:54:40.000000000 +0000 @@ -24,6 +24,8 @@ public final class QNm extends Item { /** URL pattern (matching Clark and EQName notation). */ private static final Pattern BIND = Pattern.compile("^((\"|')(.*?)\\2:|Q?(\\{(.*?)\\}))(.+)$"); + /** Expanded QName pattern. */ + private static final Pattern EQNAME = Pattern.compile("(Q\\{([^}]*)\\})?([^}]+)"); /** Singleton instance. */ private static final QNmMap CACHE = new QNmMap(); @@ -112,7 +114,7 @@ * Resolves a QName string. * @param name name to resolve * @param def default namespace (can be {@code null}) - * @param sc static context + * @param sc static context (can be {@code null}) * @param info input info * @return string * @throws QueryException query exception @@ -122,7 +124,7 @@ // check for namespace declaration final Matcher m = BIND.matcher(Token.string(name)); - final byte[] uri; + byte[] uri = null; byte[] nm = name; if(m.find()) { final String u = m.group(3); @@ -133,7 +135,7 @@ if(i == -1) { uri = def; } else { - uri = sc.ns.uri(substring(nm, 0, i)); + if(sc != null) uri = sc.ns.uri(substring(nm, 0, i)); if(uri == null) throw NOURI_X.get(info, name); } } @@ -142,6 +144,17 @@ } /** + * Resolves an expanded QName string. + * @param name string + * @return resulting QName, or {@code null} + */ + public static QNm resolve(final byte[] name) { + final Matcher m = EQNAME.matcher(Token.string(name)); + return m.matches() ? new QNm(m.group(3), m.group(1) == null || + m.group(2).isEmpty() ? null : m.group(2)) : null; + } + + /** * Sets the URI of this QName. * @param u the uri to be set */ @@ -278,7 +291,7 @@ public byte[] prefixId(final byte[] ns) { if(ns != null && Token.eq(uri(), ns)) return local(); final byte[] p = NSGlobal.prefix(uri()); - return p.length == 0 ? id() : concat(p, token(":"), local()); + return p.length == 0 ? id() : Token.concat(p, token(":"), local()); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/map/Branch.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/map/Branch.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/map/Branch.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/map/Branch.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,6 @@ package org.basex.query.value.map; import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.util.collation.*; import org.basex.query.value.*; import org.basex.query.value.item.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/map/Leaf.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/map/Leaf.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/map/Leaf.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/map/Leaf.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,6 @@ package org.basex.query.value.map; import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.util.collation.*; import org.basex.query.value.*; import org.basex.query.value.item.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/map/List.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/map/List.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/map/List.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/map/List.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,7 +3,6 @@ import static org.basex.query.QueryText.*; import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.util.collation.*; import org.basex.query.value.*; import org.basex.query.value.item.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/map/Map.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/map/Map.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/map/Map.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/map/Map.java 2015-07-14 10:54:40.000000000 +0000 @@ -192,7 +192,7 @@ */ public Value keys() { if(keys == null) { - final ValueBuilder res = new ValueBuilder(root.size); + final ValueBuilder res = new ValueBuilder(); root.keys(res); keys = res.value(); } @@ -203,10 +203,10 @@ * All values defined in this map. * @return list of keys */ - public ValueBuilder values() { - final ValueBuilder res = new ValueBuilder(root.size); + public Value values() { + final ValueBuilder res = new ValueBuilder(); root.values(res); - return res; + return res.value(); } /** @@ -217,11 +217,11 @@ * @return resulting value * @throws QueryException query exception */ - public ValueBuilder apply(final FItem func, final QueryContext qc, final InputInfo ii) + public Value apply(final FItem func, final QueryContext qc, final InputInfo ii) throws QueryException { - final ValueBuilder vb = new ValueBuilder(root.size); + final ValueBuilder vb = new ValueBuilder(); root.apply(vb, func, qc, ii); - return vb; + return vb.value(); } @Override diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/map/TrieNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/map/TrieNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/map/TrieNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/map/TrieNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -2,7 +2,6 @@ import org.basex.query.*; import org.basex.query.func.fn.*; -import org.basex.query.iter.*; import org.basex.query.util.collation.*; import org.basex.query.value.*; import org.basex.query.value.item.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/ANode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/ANode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/ANode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/ANode.java 2015-07-14 10:54:40.000000000 +0000 @@ -160,11 +160,11 @@ final Atts ns = new Atts(); ANode node = this; do { - final Atts n = node.namespaces(); - if(n != null) { - for(int a = n.size() - 1; a >= 0; a--) { - final byte[] key = n.name(a); - if(!ns.contains(key)) ns.add(key, n.value(a)); + final Atts nsp = node.namespaces(); + if(nsp != null) { + for(int a = nsp.size() - 1; a >= 0; a--) { + final byte[] key = nsp.name(a); + if(!ns.contains(key)) ns.add(key, nsp.value(a)); } } node = node.parent(); @@ -236,7 +236,7 @@ if(!nl.get(i).is(n)) continue; // check which node appears as first LCA child final ANode c1 = nl.get(i - 1); - final AxisMoreIter ir = n.children(); + final BasicNodeIter ir = n.children(); for(ANode c; (c = ir.next()) != null;) { if(c.is(c1)) return -1; if(c.is(c2)) return 1; @@ -259,6 +259,15 @@ } /** + * Returns the root of a node (the topmost ancestor without parent node). + * @return root node + */ + public final ANode root() { + final ANode p = parent(); + return p == null ? this : p.root(); + } + + /** * Returns the parent node. * @return parent node */ @@ -266,10 +275,10 @@ /** * Sets the parent node. - * @param p parent node + * @param par parent node * @return self reference */ - protected abstract ANode parent(final ANode p); + protected abstract ANode parent(final ANode par); /** * Returns true if the node has children. @@ -301,9 +310,9 @@ * @return attribute value */ public byte[] attribute(final QNm name) { - final AxisIter ai = attributes(); + final BasicNodeIter iter = attributes(); while(true) { - final ANode node = ai.next(); + final ANode node = iter.next(); if(node == null) return null; if(node.qname().eq(name)) return node.string(); } @@ -311,118 +320,129 @@ /** * Returns an ancestor axis iterator. + * If nodes returned are to be further used, they must be finalized via {@link ANode#finish()}. * @return iterator */ - public abstract AxisIter ancestor(); + public abstract BasicNodeIter ancestor(); /** * Returns an ancestor-or-self axis iterator. + * If nodes returned are to be further used, they must be finalized via {@link ANode#finish()}. * @return iterator */ - public abstract AxisIter ancestorOrSelf(); + public abstract BasicNodeIter ancestorOrSelf(); /** * Returns an attribute axis iterator. + * If nodes returned are to be further used, they must be finalized via {@link ANode#finish()}. * @return iterator */ - public abstract AxisMoreIter attributes(); + public abstract BasicNodeIter attributes(); /** * Returns a child axis iterator. + * If nodes returned are to be further used, they must be finalized via {@link ANode#finish()}. * @return iterator */ - public abstract AxisMoreIter children(); + public abstract BasicNodeIter children(); /** * Returns a descendant axis iterator. + * If nodes returned are to be further used, they must be finalized via {@link ANode#finish()}. * @return iterator */ - public abstract AxisIter descendant(); + public abstract BasicNodeIter descendant(); /** * Returns a descendant-or-self axis iterator. + * If nodes returned are to be further used, they must be finalized via {@link ANode#finish()}. * @return iterator */ - public abstract AxisIter descendantOrSelf(); + public abstract BasicNodeIter descendantOrSelf(); /** * Returns a following axis iterator. + * If nodes returned are to be further used, they must be finalized via {@link ANode#finish()}. * @return iterator */ - public abstract AxisIter following(); + public abstract BasicNodeIter following(); /** * Returns a following-sibling axis iterator. + * If nodes returned are to be further used, they must be finalized via {@link ANode#finish()}. * @return iterator */ - public abstract AxisIter followingSibling(); + public abstract BasicNodeIter followingSibling(); /** * Returns a parent axis iterator. + * If nodes returned are to be further used, they must be finalized via {@link ANode#finish()}. * @return iterator */ - public abstract AxisIter parentIter(); + public abstract BasicNodeIter parentIter(); /** * Returns a preceding axis iterator. + * If nodes returned are to be further used, they must be finalized via {@link ANode#finish()}. * @return iterator */ - public final AxisIter preceding() { - return new AxisIter() { + public final BasicNodeIter preceding() { + return new BasicNodeIter() { /** Iterator. */ - private NodeSeqBuilder nc; + private BasicNodeIter iter; @Override public ANode next() { - if(nc == null) { - nc = new NodeSeqBuilder(); + if(iter == null) { + final ANodeList list = new ANodeList(); ANode n = ANode.this; ANode p = n.parent(); while(p != null) { if(n.type != NodeType.ATT) { - final NodeSeqBuilder tmp = new NodeSeqBuilder(); - final AxisIter ai = p.children(); - for(ANode c; (c = ai.next()) != null && !c.is(n);) { + final ANodeList tmp = new ANodeList(); + final BasicNodeIter ir = p.children(); + for(ANode c; (c = ir.next()) != null && !c.is(n);) { tmp.add(c.finish()); addDesc(c.children(), tmp); } - for(long t = tmp.size() - 1; t >= 0; t--) nc.add(tmp.get(t)); + for(int t = tmp.size() - 1; t >= 0; t--) list.add(tmp.get(t)); } n = p; p = p.parent(); } + iter = list.iter(); } - return nc.next(); + return iter.next(); } }; } /** * Returns a preceding-sibling axis iterator. + * If nodes returned are to be further used, they must be finalized via {@link ANode#finish()}. * @return iterator */ - public final AxisIter precedingSibling() { - return new AxisIter() { + public final BasicNodeIter precedingSibling() { + return new BasicNodeIter() { /** Child nodes. */ - private NodeSeqBuilder nc; + private BasicNodeIter iter; /** Counter. */ - private long c; + private int i; @Override public ANode next() { - if(nc == null) { + if(iter == null) { if(type == NodeType.ATT) return null; final ANode r = parent(); if(r == null) return null; - nc = new NodeSeqBuilder(); - final AxisIter ai = r.children(); - for(ANode n; (n = ai.next()) != null && !n.is(ANode.this);) { - nc.add(n.finish()); - } - c = nc.size(); + final ANodeList list = new ANodeList(); + final BasicNodeIter ir = r.children(); + for(ANode n; (n = ir.next()) != null && !n.is(ANode.this);) list.add(n.finish()); + i = list.size(); + iter = list.iter(); } - return c > 0 ? nc.get(--c) : null; + return i > 0 ? iter.get(--i) : null; } }; } @@ -431,18 +451,16 @@ * Returns an self axis iterator. * @return iterator */ - public final AxisMoreIter self() { - return new AxisMoreIter() { - /** First call. */ - private boolean more = true; + public final BasicNodeIter self() { + return new BasicNodeIter() { + /** Flag. */ + private boolean all; @Override - public boolean more() { - return more; - } - @Override public ANode next() { - return (more ^= true) ? null : ANode.this; + if(all) return null; + all = true; + return ANode.this; } }; } @@ -450,12 +468,12 @@ /** * Adds children of a sub node. * @param ch child nodes - * @param nc node cache + * @param nb node cache */ - static void addDesc(final AxisMoreIter ch, final NodeSeqBuilder nc) { + static void addDesc(final BasicNodeIter ch, final ANodeList nb) { for(ANode n; (n = ch.next()) != null;) { - nc.add(n.finish()); - addDesc(n.children(), nc); + nb.add(n.finish()); + addDesc(n.children(), nb); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/DBNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/DBNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/DBNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/DBNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -30,11 +30,9 @@ */ public class DBNode extends ANode { /** Data reference. */ - public final Data data; + private final Data data; /** Pre value. */ - public int pre; - /** Namespaces. */ - private Atts nsp; + private int pre; /** * Constructor, creating a document node from the specified data reference. @@ -104,7 +102,6 @@ type = type(k); parent = null; value = null; - nsp = null; pre = p; } @@ -113,6 +110,22 @@ return data; } + /** + * Assigns a pre value. + * @param p pre value + */ + public final void pre(final int p) { + pre = p; + } + + /** + * Returns the pre value. + * @return pre value + */ + public final int pre() { + return pre; + } + @Override public final byte[] string() { if(value == null) value = data.atom(pre); @@ -162,23 +175,14 @@ @Override public final QNm qname(final QNm name) { // update the name and uri strings in the specified QName - final byte[] nm = name(); - byte[] uri = Token.EMPTY; - final boolean pref = Token.indexOf(nm, ':') != -1; - if(pref || data.nspaces.size() != 0) { - final int n = pref ? data.nspaces.uri(nm, pre, data) : - data.uri(pre, data.kind(pre)); - final byte[] u = n > 0 ? data.nspaces.uri(n) : pref ? NSGlobal.uri(Token.prefix(nm)) : null; - if(u != null) uri = u; - } - name.set(nm, uri); + final byte[][] qname = data.qname(pre, kind()); + name.set(qname[0], qname[1]); return name; } @Override public final Atts namespaces() { - if(type == NodeType.ELM && nsp == null) nsp = data.ns(pre); - return nsp; + return data.namespaces(pre); } @Override @@ -257,180 +261,171 @@ } @Override - protected final DBNode parent(final ANode p) { - parent = p; + protected final DBNode parent(final ANode par) { + parent = par; return this; } @Override public final boolean hasChildren() { - final int k = data.kind(pre); - return data.attSize(pre, k) != data.size(pre, k); + final int kind = data.kind(pre); + return data.attSize(pre, kind) != data.size(pre, kind); } @Override - public final AxisIter ancestor() { - return new AxisIter() { + public final BasicNodeIter ancestor() { + return new BasicNodeIter() { private final DBNode node = copy(); - int p = pre, k = data.kind(p); + int curr = pre, kind = data.kind(curr); @Override public ANode next() { - p = data.parent(p, k); - if(p == -1) return null; - k = data.kind(p); - node.set(p, k); + curr = data.parent(curr, kind); + if(curr == -1) return null; + kind = data.kind(curr); + node.set(curr, kind); return node; } }; } @Override - public final AxisIter ancestorOrSelf() { - return new AxisIter() { + public final BasicNodeIter ancestorOrSelf() { + return new BasicNodeIter() { private final DBNode node = copy(); - int p = pre, k = data.kind(p); + int curr = pre, kind = data.kind(curr); @Override public ANode next() { - if(p == -1) return null; - k = data.kind(p); - node.set(p, k); - p = data.parent(p, k); + if(curr == -1) return null; + kind = data.kind(curr); + node.set(curr, kind); + curr = data.parent(curr, kind); return node; } }; } @Override - public final AxisMoreIter attributes() { - return new AxisMoreIter() { + public final BasicNodeIter attributes() { + return new BasicNodeIter() { final DBNode node = copy(); - final int s = pre + data.attSize(pre, data.kind(pre)); - int p = pre + 1; - - @Override - public boolean more() { - return p != s; - } + final int last = pre + data.attSize(pre, data.kind(pre)); + int curr = pre + 1; @Override public DBNode next() { - if(!more()) return null; - node.set(p++, Data.ATTR); - return node; + if(curr == last) return null; + final DBNode n = node; + n.set(curr++, Data.ATTR); + return n; } }; } @Override - public final AxisMoreIter children() { - return new AxisMoreIter() { - int k = data.kind(pre), p = pre + data.attSize(pre, k); - final int s = pre + data.size(pre, k); + public final BasicNodeIter children() { + return new BasicNodeIter() { + int kind = data.kind(pre), curr = pre + data.attSize(pre, kind); + final int last = pre + data.size(pre, kind); final DBNode node = copy(); @Override - public boolean more() { - return p < s; - } - - @Override public ANode next() { - if(!more()) return null; - k = data.kind(p); - node.set(p, k); - p += data.size(p, k); + if(curr == last) return null; + final Data d = data; + kind = d.kind(curr); + node.set(curr, kind); + curr += d.size(curr, kind); return node; } }; } @Override - public final AxisIter descendant() { - return new AxisIter() { - int k = data.kind(pre), p = pre + data.attSize(pre, k); - final int s = pre + data.size(pre, k); + public final BasicNodeIter descendant() { + return new BasicNodeIter() { + int kind = data.kind(pre), curr = pre + data.attSize(pre, kind); + final int last = pre + data.size(pre, kind); final DBNode node = copy(); @Override public DBNode next() { - if(p == s) return null; - k = data.kind(p); - node.set(p, k); - p += data.attSize(p, k); + if(curr == last) return null; + kind = data.kind(curr); + node.set(curr, kind); + curr += data.attSize(curr, kind); return node; } }; } @Override - public final AxisIter descendantOrSelf() { - return new AxisIter() { + public final BasicNodeIter descendantOrSelf() { + return new BasicNodeIter() { final DBNode node = copy(); - final int s = pre + data.size(pre, data.kind(pre)); - int p = pre; + final int last = pre + data.size(pre, data.kind(pre)); + int curr = pre; @Override public ANode next() { - if(p == s) return null; - final int k = data.kind(p); - node.set(p, k); - p += data.attSize(p, k); + if(curr == last) return null; + final int k = data.kind(curr); + node.set(curr, k); + curr += data.attSize(curr, k); return node; } }; } @Override - public final AxisIter following() { - return new AxisIter() { + public final BasicNodeIter following() { + return new BasicNodeIter() { private final DBNode node = copy(); - final int s = data.meta.size; - int k = data.kind(pre); - int p = pre + data.size(pre, k); + final int sz = data.meta.size; + int kind = data.kind(pre); + int curr = pre + data.size(pre, kind); @Override public ANode next() { - if(p == s) return null; - k = data.kind(p); - node.set(p, k); - p += data.attSize(p, k); + if(curr == sz) return null; + kind = data.kind(curr); + node.set(curr, kind); + curr += data.attSize(curr, kind); return node; } }; } @Override - public final AxisIter followingSibling() { - return new AxisIter() { + public final BasicNodeIter followingSibling() { + return new BasicNodeIter() { private final DBNode node = copy(); - int k = data.kind(pre); - private final int pp = data.parent(pre, k); - final int s = pp == -1 ? 0 : pp + data.size(pp, data.kind(pp)); - int p = pp == -1 ? 0 : pre + data.size(pre, k); + int kind = data.kind(pre); + private final int pp = data.parent(pre, kind); + final int sz = pp == -1 ? 0 : pp + data.size(pp, data.kind(pp)); + int curr = pp == -1 ? 0 : pre + data.size(pre, kind); @Override public ANode next() { - if(p == s) return null; - k = data.kind(p); - node.set(p, k); - p += data.size(p, k); + if(curr == sz) return null; + kind = data.kind(curr); + node.set(curr, kind); + curr += data.size(curr, kind); return node; } }; } @Override - public final AxisIter parentIter() { - return new AxisIter() { - /** First call. */ - private boolean more; + public final BasicNodeIter parentIter() { + return new BasicNodeIter() { + private boolean all; @Override public ANode next() { - if(more) return null; - more = true; + if(all) return null; + all = true; return parent(); } }; @@ -459,8 +454,9 @@ // check if a document has a single element as child ID t = type.id(); if(type == NodeType.DOC) { - final AxisMoreIter ai = children(); - if(ai.more() && ai.next().type == NodeType.ELM && !ai.more()) t = NodeType.DEL.id(); + final BasicNodeIter ir = children(); + final ANode n = ir.next(); + if(n != null && n.type == NodeType.ELM && ir.next() == null) t = NodeType.DEL.id(); } return t; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/FDoc.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/FDoc.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/FDoc.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/FDoc.java 2015-07-14 10:54:40.000000000 +0000 @@ -96,7 +96,7 @@ } @Override - public AxisMoreIter children() { + public BasicNodeIter children() { return iter(children); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/FElem.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/FElem.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/FElem.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/FElem.java 2015-07-14 10:54:40.000000000 +0000 @@ -93,16 +93,7 @@ * @param name element name */ public FElem(final QNm name) { - this(name, null); - } - - /** - * Constructor for creating an element with namespace declarations. - * @param name element name - * @param ns namespaces - */ - private FElem(final QNm name, final Atts ns) { - this(name, ns, null, null); + this(name, null, null, null); } /** @@ -381,12 +372,12 @@ } @Override - public AxisMoreIter attributes() { + public BasicNodeIter attributes() { return atts != null ? iter(atts) : super.attributes(); } @Override - public AxisMoreIter children() { + public BasicNodeIter children() { return children != null ? iter(children) : super.children(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/FNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/FNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/FNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/FNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,5 +1,7 @@ package org.basex.query.value.node; +import java.util.*; + import org.basex.core.*; import org.basex.query.iter.*; import org.basex.query.util.list.*; @@ -59,8 +61,8 @@ } @Override - public final AxisIter ancestor() { - return new AxisIter() { + public final BasicNodeIter ancestor() { + return new BasicNodeIter() { private ANode node = FNode.this; @Override @@ -72,8 +74,8 @@ } @Override - public final AxisIter ancestorOrSelf() { - return new AxisIter() { + public final BasicNodeIter ancestorOrSelf() { + return new BasicNodeIter() { private ANode node = FNode.this; @Override @@ -87,18 +89,18 @@ } @Override - public AxisMoreIter attributes() { - return AxisMoreIter.EMPTY; + public BasicNodeIter attributes() { + return BasicNodeIter.EMPTY; } @Override - public AxisMoreIter children() { - return AxisMoreIter.EMPTY; + public BasicNodeIter children() { + return BasicNodeIter.EMPTY; } @Override - public final FNode parent(final ANode p) { - parent = p; + public final FNode parent(final ANode par) { + parent = par; return this; } @@ -108,12 +110,12 @@ } @Override - public final AxisIter descendant() { + public final BasicNodeIter descendant() { return desc(false); } @Override - public final AxisIter descendantOrSelf() { + public final BasicNodeIter descendantOrSelf() { return desc(true); } @@ -122,13 +124,11 @@ * @param iter iterator * @return node iterator */ - static AxisMoreIter iter(final ANodeList iter) { - return new AxisMoreIter() { - /** Child counter. */ int c; - @Override - public boolean more() { return iter != null && c != iter.size(); } + static BasicNodeIter iter(final ANodeList iter) { + return new BasicNodeIter() { + int c; @Override - public ANode next() { return more() ? iter.get(c++) : null; } + public ANode next() { return iter != null && c != iter.size() ? iter.get(c++) : null; } @Override public ANode get(final long i) { return iter.get((int) i); } @Override @@ -159,90 +159,84 @@ * @param self include self node * @return node iterator */ - private AxisIter desc(final boolean self) { - return new AxisIter() { - /** Iterator. */ - private AxisMoreIter[] nm = new AxisMoreIter[1]; - /** Iterator Level. */ - private int l; + private BasicNodeIter desc(final boolean self) { + return new BasicNodeIter() { + private final Stack iters = new Stack<>(); + private ANode last; @Override public ANode next() { - if(nm[0] == null) nm[0] = self ? self() : children(); - if(l < 0) return null; - - final ANode node = nm[l].next(); - if(node != null) { - final AxisMoreIter ch = node.children(); - if(ch.more()) { - if(l + 1 == nm.length) nm = Array.copy(nm, new AxisMoreIter[l + 1 << 1]); - nm[++l] = ch; - } else { - while(!nm[l].more()) if(l-- <= 0) break; + final BasicNodeIter iter = last != null ? last.children() : self ? self() : children(); + last = iter.next(); + if(last == null) { + while(!iters.isEmpty()) { + last = iters.peek().next(); + if(last != null) break; + iters.pop(); } + } else { + iters.add(iter); } - return node; + return last; } }; } @Override - public final AxisIter parentIter() { - return new AxisIter() { - /** First call. */ - private boolean more; + public final BasicNodeIter parentIter() { + return new BasicNodeIter() { + private boolean all; @Override public ANode next() { - return (more ^= true) ? parent : null; + if(all) return null; + all = true; + return parent; } }; } @Override - public final AxisIter followingSibling() { - return new AxisIter() { - /** Iterator. */ - private AxisIter ai; + public final BasicNodeIter followingSibling() { + return new BasicNodeIter() { + private BasicNodeIter iter; @Override public ANode next() { - if(ai == null) { + if(iter == null) { final ANode r = parent(); if(r == null) return null; - ai = r.children(); - for(ANode n; (n = ai.next()) != null && !n.is(FNode.this);); + iter = r.children(); + for(ANode n; (n = iter.next()) != null && !n.is(FNode.this);); } - return ai.next(); + return iter.next(); } }; } @Override - public final AxisIter following() { - return new AxisIter() { - /** Iterator. */ - private NodeSeqBuilder nc; + public final BasicNodeIter following() { + return new BasicNodeIter() { + private BasicNodeIter iter; @Override public ANode next() { - if(nc == null) { - nc = new NodeSeqBuilder(); - ANode n = FNode.this; - ANode p = n.parent(); - while(p != null) { - final AxisIter i = p.children(); - for(ANode c; n.type != NodeType.ATT && - (c = i.next()) != null && !c.is(n);); - for(ANode c; (c = i.next()) != null;) { - nc.add(c.finish()); - addDesc(c.children(), nc); + if(iter == null) { + final ANodeList list = new ANodeList(); + ANode node = FNode.this, par = node.parent(); + while(par != null) { + final BasicNodeIter i = par.children(); + for(ANode n; node.type != NodeType.ATT && (n = i.next()) != null && !n.is(node);); + for(ANode n; (n = i.next()) != null;) { + list.add(n.finish()); + addDesc(n.children(), list); } - n = p; - p = p.parent(); + node = par; + par = par.parent(); } + iter = list.iter(); } - return nc.next(); + return iter.next(); } }; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/FTNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/FTNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/FTNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/FTNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,6 +1,7 @@ package org.basex.query.value.node; import org.basex.data.*; +import org.basex.query.util.ft.*; import org.basex.query.value.type.*; import org.basex.util.ft.*; @@ -16,47 +17,63 @@ /** Total number of indexed results. */ private final int is; /** Full-text matches. */ - public FTMatches all; + private FTMatches matches; /** * Constructor, called by the sequential variant. - * @param all matches + * @param matches matches * @param score scoring */ - public FTNode(final FTMatches all, final double score) { - this(all, null, 0, 0, 0, score); + public FTNode(final FTMatches matches, final double score) { + this(matches, null, 0, 0, 0, score); } /** * Constructor, called by the index variant. - * @param all full-text matches + * @param matches full-text matches * @param d data reference * @param p pre value * @param tl token length * @param is number of indexed results * @param score score value out of the index */ - public FTNode(final FTMatches all, final Data d, final int p, final int tl, final int is, + public FTNode(final FTMatches matches, final Data d, final int p, final int tl, final int is, final double score) { super(d, p, null, NodeType.TXT); - this.all = all; + this.matches = matches; this.tl = tl; this.is = is; if(score != -1) this.score = score; } + /** + * Assigns full-text matches. + * @param match full-text matches + */ + public void matches(final FTMatches match) { + matches = match; + } + + /** + * Returns full-text matches. + * @return full-text matches + */ + public FTMatches matches() { + return matches; + } + @Override public double score() { if(score == null) { - if(all == null) return 0; - score = Scoring.textNode(all.size(), is, tl, data.textLen(pre, true)); + if(matches == null) return 0; + score = Scoring.textNode(matches.size(), is, tl, data().textLen(pre(), true)); } return score; } @Override public String toString() { - return super.toString() + (all != null ? " (" + all.size() + ')' : ""); + return super.toString() + (matches != null ? " (" + matches.size() + ')' : ""); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/FTPosNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/FTPosNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/node/FTPosNode.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/node/FTPosNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,6 +1,7 @@ package org.basex.query.value.node; import org.basex.data.*; +import org.basex.query.util.ft.*; /** * Database node with full-text positions. diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/BlnSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/BlnSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/BlnSeq.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/BlnSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,7 +9,7 @@ import org.basex.query.value.type.*; /** - * Sequence of items of type {@link Int xs:boolean}, containing at least two of them. + * Sequence of items of type {@link Bln xs:boolean}, containing at least two of them. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen @@ -42,14 +42,6 @@ return values; } - @Override - public Value reverse() { - final int s = values.length; - final boolean[] tmp = new boolean[s]; - for(int l = 0, r = s - 1; l < s; l++, r--) tmp[l] = values[r]; - return get(tmp); - } - // STATIC METHODS ===================================================================== /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/BytSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/BytSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/BytSeq.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/BytSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -41,14 +41,6 @@ return values; } - @Override - public Value reverse() { - final int s = values.length; - final byte[] tmp = new byte[s]; - for(int l = 0, r = s - 1; l < s; l++, r--) tmp[l] = values[r]; - return get(tmp); - } - // STATIC METHODS ===================================================================== /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/DblSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/DblSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/DblSeq.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/DblSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -42,14 +42,6 @@ return values; } - @Override - public Value reverse() { - final int s = values.length; - final double[] tmp = new double[s]; - for(int l = 0, r = s - 1; l < s; l++, r--) tmp[l] = values[r]; - return get(tmp); - } - // STATIC METHODS ===================================================================== /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/DBNodeSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/DBNodeSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/DBNodeSeq.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/DBNodeSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,7 +6,6 @@ import org.basex.data.*; import org.basex.query.*; import org.basex.query.expr.*; -import org.basex.query.iter.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -20,22 +19,22 @@ * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ -public final class DBNodeSeq extends NativeSeq { +public class DBNodeSeq extends NativeSeq { /** Data reference. */ - private final Data data; + protected final Data data; /** Pre values. */ - public final int[] pres; - /** Pre values comprise all documents of the database. */ - public final boolean all; + protected int[] pres; + /** Pre values reference all documents of the database. */ + protected boolean all; /** * Constructor. * @param pres pre values * @param data data reference * @param type node type - * @param all pre values comprise all documents of the database + * @param all pre values reference all documents of the database */ - private DBNodeSeq(final int[] pres, final Data data, final Type type, final boolean all) { + protected DBNodeSeq(final int[] pres, final Data data, final Type type, final boolean all) { super(pres.length, type); this.pres = pres; this.data = data; @@ -70,20 +69,37 @@ } @Override - public Value reverse() { - final int s = pres.length; - final int[] tmp = new int[s]; - for(int l = 0, r = s - 1; l < s; l++, r--) tmp[l] = pres[r]; - return get(tmp, data, type, false); - } - - @Override public Value atomValue(final InputInfo ii) throws QueryException { - final ValueBuilder vb = new ValueBuilder((int) size); + final ValueBuilder vb = new ValueBuilder(); for(int s = 0; s < size; s++) vb.add(itemAt(s).atomValue(ii)); return vb.value(); } + /** + * Returns the internal pre value array. + * @return pre values + */ + public int[] pres() { + return pres; + } + + /** + * Returns the specified pre value. + * @param index index of pre value + * @return pre value + */ + public int pre(final int index) { + return pres[index]; + } + + /** + * Indicates if pre values reference all documents of the database. + * @return flag + */ + public boolean all() { + return all; + } + @Override public String toString() { final StringBuilder sb = new StringBuilder(PAREN1); @@ -105,7 +121,7 @@ * @param pres pre values * @param data data reference * @param docs all values reference document nodes - * @param all pre values comprise all documents of the database + * @param all pre values reference all documents of the database * @return resulting item or sequence */ public static Value get(final IntList pres, final Data data, final boolean docs, @@ -118,7 +134,7 @@ * @param pres pre values * @param data data reference * @param type node type - * @param all pre values comprise all documents of the database + * @param all pre values reference all documents of the database * @return resulting item or sequence */ private static Value get(final int[] pres, final Data data, final Type type, final boolean all) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/DBNodes.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/DBNodes.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/DBNodes.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/DBNodes.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,207 @@ +package org.basex.query.value.seq; + +import java.util.*; + +import org.basex.core.Context; +import org.basex.data.*; +import org.basex.query.*; +import org.basex.query.expr.*; +import org.basex.query.util.ft.*; +import org.basex.query.value.node.*; +import org.basex.query.value.type.*; +import org.basex.util.list.*; + +/** + * This class stores database nodes in an ascending order. + * Instances of this class will be returned by the method {@link QueryProcessor#cache(int)} method. + * They are used in the GUI and in the {@link Context} class to reference currently opened, + * marked, and copied database nodes. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class DBNodes extends DBNodeSeq { + /** Full-text position data (can be {@code null}). */ + private FTPosData ftpos; + /** Sorted pre values (can be {@code null}). */ + private int[] sorted; + + /** + * Constructor, specifying a database and pre values. + * @param data data reference + * @param pres pre values + */ + public DBNodes(final Data data, final int... pres) { + this(data, false, pres); + } + + /** + * Constructor, specifying a database, pre values and full-text positions. + * @param all pre values reference all documents of the database + * @param data data reference + * @param pres pre values + */ + public DBNodes(final Data data, final boolean all, final int... pres) { + super(pres, data, all ? NodeType.DOC : NodeType.NOD, all); + } + + /** + * Assigns full-text position data. + * @param ft full-text positions + * @return self reference + */ + public DBNodes ftpos(final FTPosData ft) { + ftpos = ft; + return this; + } + + /** + * Returns full-text position data. + * @return position data + */ + public FTPosData ftpos() { + return ftpos; + } + + @Override + public boolean sameAs(final Expr cmp) { + if(!(cmp instanceof DBNodes)) return false; + final DBNodes n = (DBNodes) cmp; + final int[] ps = pres, ps2 = n.pres; + final int pl = ps.length; + if(pl != ps2.length || data != n.data) return false; + for(int p = 0; p < pl; ++p) if(ps2[p] != ps[p]) return false; + return ftpos == null ? n.ftpos == null : ftpos.sameAs(n.ftpos); + } + + /** + * Returns {@code null} if the pre values reference all documents of the database. + * @return self reference or {@code null} + */ + public DBNodes discardDocs() { + if(all) return null; + + final IntList docs = data.resources.docs(); + final int[] ps = pres; + final int pl = ps.length; + if(pl != docs.size()) return this; + + int c = -1; + while(++c < pl && ps[c] == docs.get(c)); + return c < pl ? this : null; + } + + /** + * Checks if the specified node is contained in the array. + * @param pre pre value + * @return true if the node was found + */ + public boolean contains(final int pre) { + return find(pre) >= 0; + } + + /** + * Returns the position of the specified node or the negative value - 1 of + * the position where it should have been found. + * @param pre pre value + * @return position, or {@code -1} + */ + public int find(final int pre) { + sort(); + return Arrays.binarySearch(sorted, pre); + } + + /** + * Adds or removes the specified pre node. + * @param pre pre value + */ + public void toggle(final int pre) { + final int[] n = { pre }; + pres = contains(pre) ? except(pres, n) : union(pres, n); + sorted = null; + } + + /** + * Merges the specified array with the existing pre nodes. + * @param pre pre value + */ + public void union(final int[] pre) { + pres = union(pres, pre); + sorted = null; + } + + /** + * Merges two sorted integer arrays via union. + * Note that the input arrays must be sorted. + * @param pres1 first set + * @param pres2 second set + * @return resulting set + */ + private static int[] union(final int[] pres1, final int[] pres2) { + final int al = pres1.length, bl = pres2.length; + final IntList il = new IntList(); + int a = 0, b = 0; + while(a != al && b != bl) { + final int d = pres1[a] - pres2[b]; + il.add(d <= 0 ? pres1[a++] : pres2[b++]); + if(d == 0) ++b; + } + while(a != al) il.add(pres1[a++]); + while(b != bl) il.add(pres2[b++]); + return il.finish(); + } + + /** + * Subtracts the second from the first array. + * Note that the input arrays must be sorted. + * @param pres1 first set + * @param pres2 second set + * @return resulting set + */ + private static int[] except(final int[] pres1, final int[] pres2) { + final int al = pres1.length, bl = pres2.length; + final IntList il = new IntList(); + int a = 0, b = 0; + while(a != al && b != bl) { + final int d = pres1[a] - pres2[b]; + if(d < 0) il.add(pres1[a]); + else ++b; + if(d <= 0) ++a; + } + while(a != al) il.add(pres1[a++]); + return il.finish(); + } + + /** + * Creates a sorted node array. If the original array is already sorted, + * the same reference is used. + */ + private void sort() { + if(sorted != null) return; + int min = Integer.MIN_VALUE; + for(final int pre : pres) { + if(pre < min) { + sorted = Arrays.copyOf(pres, pres.length); + Arrays.sort(sorted); + return; + } + min = pre; + } + sorted = pres; + } + + /** + * Returns a sorted pre value. + * @param index index of pre value + * @return pre value + */ + public int sorted(final int index) { + return sorted[index]; + } + + @Override + public DBNode itemAt(final long pos) { + final int pre = pres[(int) pos]; + return ftpos == null ? new DBNode(data, pre) : new FTPosNode(data, pre, ftpos); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/DecSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/DecSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/DecSeq.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/DecSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -43,14 +43,6 @@ return values; } - @Override - public Value reverse() { - final int s = values.length; - final BigDecimal[] tmp = new BigDecimal[s]; - for(int l = 0, r = s - 1; l < s; l++, r--) tmp[l] = values[r]; - return get(tmp); - } - // STATIC METHODS ===================================================================== /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/Empty.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/Empty.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/Empty.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/Empty.java 2015-07-14 10:54:40.000000000 +0000 @@ -100,11 +100,21 @@ } @Override + public Value reverse() { + return this; + } + + @Override public boolean homogeneous() { return true; } @Override + public Value subSeq(final long start, final long len) { + return this; + } + + @Override public Value materialize(final InputInfo ii) { return this; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/FltSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/FltSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/FltSeq.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/FltSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -42,14 +42,6 @@ return values; } - @Override - public Value reverse() { - final int s = values.length; - final float[] tmp = new float[s]; - for(int l = 0, r = s - 1; l < s; l++, r--) tmp[l] = values[r]; - return get(tmp); - } - // STATIC METHODS ===================================================================== /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/IntSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/IntSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/IntSeq.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/IntSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -62,14 +62,6 @@ } } - @Override - public Value reverse() { - final int s = values.length; - final long[] tmp = new long[s]; - for(int l = 0, r = s - 1; l < s; l++, r--) tmp[l] = values[r]; - return get(tmp, type); - } - // STATIC METHODS ===================================================================== /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/ItemSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/ItemSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/ItemSeq.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/ItemSeq.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,129 +0,0 @@ -package org.basex.query.value.seq; - -import static org.basex.query.QueryError.*; - -import org.basex.query.*; -import org.basex.query.expr.*; -import org.basex.query.iter.*; -import org.basex.query.value.*; -import org.basex.query.value.item.*; -import org.basex.query.value.node.*; -import org.basex.query.value.type.*; -import org.basex.query.value.type.SeqType.Occ; -import org.basex.util.*; - -/** - * Sequence, containing at least two items. - * - * @author BaseX Team 2005-15, BSD License - * @author Christian Gruen - */ -final class ItemSeq extends Seq { - /** Item array. */ - private final Item[] items; - /** Item Types. */ - private Type ret; - - /** - * Constructor. - * @param items items - * @param size size - * @param ret sequence type - */ - ItemSeq(final Item[] items, final int size, final Type ret) { - super(size); - this.items = items; - this.ret = ret; - } - - @Override - public Item ebv(final QueryContext qc, final InputInfo ii) throws QueryException { - if(items[0] instanceof ANode) return items[0]; - throw EBV_X.get(ii, this); - } - - @Override - public SeqType seqType() { - if(ret == null) { - Type t = items[0].type; - for(int s = 1; s < size; s++) { - if(t != items[s].type) { - t = AtomType.ITEM; - break; - } - } - ret = t; - type = t; - } - return SeqType.get(ret, Occ.ONE_MORE); - } - - @Override - public boolean iterable() { - return false; - } - - @Override - public boolean sameAs(final Expr cmp) { - if(!(cmp instanceof ItemSeq)) return false; - final ItemSeq is = (ItemSeq) cmp; - return items == is.items && size == is.size; - } - - @Override - public int writeTo(final Item[] arr, final int index) { - System.arraycopy(items, 0, arr, index, (int) size); - return (int) size; - } - - @Override - public Item itemAt(final long pos) { - return items[(int) pos]; - } - - @Override - public boolean homogeneous() { - return ret != null && ret != AtomType.ITEM; - } - - @Override - public Value reverse() { - final int s = items.length; - final Item[] tmp = new Item[s]; - for(int l = 0, r = s - 1; l < s; l++, r--) tmp[l] = items[r]; - return get(tmp, s, type); - } - - @Override - public boolean has(final Flag flag) { - if(flag == Flag.UPD) { - for(int l = 0; l < size; l++) { - if(items[l].has(Flag.UPD)) return true; - } - } - return false; - } - - @Override - public Value materialize(final InputInfo ii) throws QueryException { - final int s = (int) size; - final ValueBuilder vb = new ValueBuilder(s); - for(int i = 0; i < s; i++) vb.add(itemAt(i).materialize(ii)); - return vb.value(); - } - - @Override - public Value atomValue(final InputInfo ii) throws QueryException { - final int s = (int) size; - final ValueBuilder vb = new ValueBuilder(s); - for(int i = 0; i < s; i++) vb.add(itemAt(i).atomValue(ii)); - return vb.value(); - } - - @Override - public long atomSize() { - long s = 0; - for(int i = 0; i < size; i++) s += itemAt(i).atomSize(); - return s; - } -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/NativeSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/NativeSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/NativeSeq.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/NativeSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -61,4 +61,22 @@ public final SeqType seqType() { return SeqType.get(type, Occ.ONE_MORE); } + + @Override + public final Value insert(final long pos, final Item item) { + return copyInsert(pos, item); + } + + @Override + public final Value remove(final long pos) { + return size == 1 ? itemAt(1 - pos) : copyRemove(pos); + } + + @Override + public final Value reverse() { + final int n = (int) size; + final ValueBuilder vb = new ValueBuilder(); + for(int i = 0; i < n; i++) vb.add(itemAt(n - i - 1)); + return vb.value(type); + } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/RangeSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/RangeSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/RangeSeq.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/RangeSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -99,6 +99,16 @@ } @Override + public Value insert(final long pos, final Item item) { + return copyInsert(pos, item); + } + + @Override + public Value remove(final long pos) { + return pos == 0 || pos == size - 1 ? subSeq(pos == 0 ? 0 : 1, size - 1) : copyRemove(pos); + } + + @Override public Value reverse() { final long s = size(); return asc ? get(start + s - 1, s, false) : get(start - s + 1, s, true); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/Seq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/Seq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/Seq.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/Seq.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,6 +8,7 @@ import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; +import org.basex.query.value.seq.tree.*; import org.basex.query.value.type.*; import org.basex.util.*; @@ -19,13 +20,13 @@ */ public abstract class Seq extends Value { /** Length. */ - final long size; + protected final long size; /** * Constructor. * @param size size */ - Seq(final long size) { + protected Seq(final long size) { this(size, AtomType.ITEM); } @@ -34,41 +35,11 @@ * @param size size * @param type type */ - Seq(final long size, final Type type) { + protected Seq(final long size, final Type type) { super(type); this.size = size; } - /** - * Returns a value representation of the specified items. - * @param value value - * @return resulting item or sequence - */ - public static Value get(final Item[] value) { - return get(value, value.length); - } - - /** - * Returns a value representation of the specified items. - * @param value value - * @param size size - * @return resulting item or sequence - */ - public static Value get(final Item[] value, final int size) { - return get(value, size, null); - } - - /** - * Returns a value representation of the specified items. - * @param value value - * @param size size - * @param type sequence type - * @return resulting item or sequence - */ - public static Value get(final Item[] value, final int size, final Type type) { - return size == 0 ? Empty.SEQ : size == 1 ? value[0] : new ItemSeq(value, size, type); - } - @Override public Object toJava() throws QueryException { final Object[] obj = new Object[(int) size]; @@ -82,6 +53,11 @@ } @Override + public final boolean isEmpty() { + return false; + } + + @Override public final Item item(final QueryContext qc, final InputInfo ii) throws QueryException { throw SEQFOUND_X.get(ii, this); } @@ -92,7 +68,7 @@ } @Override - public final ValueIter iter() { + public ValueIter iter() { return new ValueIter() { int c; @Override @@ -107,6 +83,67 @@ } @Override + public Value subSeq(final long start, final long len) { + return len == 0 ? Empty.SEQ + : len == 1 ? itemAt(start) + : len < size ? new SubSeq(this, start, len) + : this; + } + + /** + * Inserts a value at the given position into this sequence and returns the resulting sequence. + * @param pos position at which the value should be inserted, must be between 0 and {@link #size} + * @param val value to insert + * @return resulting value + */ + public Value insertBefore(final long pos, final Value val) { + final long n = val.size(); + return n == 1 ? insert(pos, (Item) val) : copyInsert(pos, val); + } + + /** + * Inserts an item at the given position into this sequence and returns the resulting sequence. + * @param pos position at which the item should be inserted, must be between 0 and {@link #size} + * @param val value to insert + * @return resulting value + */ + public abstract Value insert(final long pos, final Item val); + + /** + * Helper for {@link #insertBefore(long, Value)} that copies all items into a {@link TreeSeq}. + * @param pos position at which the value should be inserted, must be between 0 and {@link #size} + * @param val value to insert + * @return resulting value + */ + final Value copyInsert(final long pos, final Value val) { + if(val.isEmpty()) return this; + final ValueBuilder vb = new ValueBuilder(); + for(long i = 0; i < pos; i++) vb.add(itemAt(i)); + vb.add(val); + for(long i = pos; i < size; i++) vb.add(itemAt(i)); + return vb.value(type); + } + + /** + * Removes the item at the given position in this sequence and returns the resulting sequence. + * @param pos position of the item to remove, must be between 0 and {@link #size} - 1 + * @return resulting sequence + */ + public abstract Value remove(final long pos); + + /** + * Helper for {@link #remove(long)} that copies all items into a {@link TreeSeq}. + * @param pos position of the item to remove, must be between 0 and {@link #size} - 1 + * @return resulting sequence + */ + final Value copyRemove(final long pos) { + final ValueBuilder vb = new ValueBuilder(); + for(long i = 0; i < pos; i++) vb.add(itemAt(i)); + for(long i = pos + 1; i < size; i++) vb.add(itemAt(i)); + return vb.value(type); + } + + @Override public final int hash(final InputInfo ii) throws QueryException { // final hash function because equivalent sequences *must* produce the // same hash value, otherwise they get lost in hash maps. @@ -122,12 +159,6 @@ throw SEQFOUND_X.get(ii, this); } - /** - * Returns a sequence in reverse order. - * @return sequence - */ - public abstract Value reverse(); - @Override public void plan(final FElem plan) { final FElem el = planElem(SIZE, size); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/StrSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/StrSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/StrSeq.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/StrSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -47,14 +47,6 @@ return tmp; } - @Override - public Value reverse() { - final int vl = values.length; - final byte[][] tmp = new byte[vl][]; - for(int l = 0, r = vl - 1; l < vl; l++, r--) tmp[l] = values[r]; - return get(tmp); - } - // STATIC METHODS ===================================================================== /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/SubSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/SubSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/SubSeq.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/SubSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,7 +3,6 @@ import static org.basex.query.QueryError.*; import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -16,50 +15,47 @@ * @author BaseX Team 2005-15, BSD License * @author Leo Woerteler */ -public final class SubSeq extends Seq { +final class SubSeq extends Seq { /** Underlying sequence. */ private final Seq sub; /** Starting index in {@link #sub}. */ private final long start; /** - * Factory method for subsequences. - * @param val underlying value - * @param from starting index - * @param len length of the subsequence - * @return the resulting value - */ - public static Value get(final Value val, final long from, final long len) { - final long vs = val.size(), n = Math.min(vs - from, len); - if(n == vs) return val; - if(n <= 0) return Empty.SEQ; - if(n == 1) return val.itemAt(from); - if(val instanceof SubSeq) { - final SubSeq ss = (SubSeq) val; - return new SubSeq(ss.sub, ss.start + from, n); - } - // cast is safe because n >= 2 - return new SubSeq((Seq) val, from, n); - } - - /** * Constructor. * @param sub underlying sequence * @param start starting index * @param len length of the subsequence */ - private SubSeq(final Seq sub, final long start, final long len) { + SubSeq(final Seq sub, final long start, final long len) { super(len, sub.type); this.sub = sub; this.start = start; } @Override + public Value subSeq(final long off, final long len) { + return len == 0 ? Empty.SEQ + : len == 1 ? sub.itemAt(start + off) + : len < size ? new SubSeq(sub, start + off, len) + : this; + } + + @Override + public Value insert(final long pos, final Item item) { + return copyInsert(pos, item); + } + + @Override + public Value remove(final long pos) { + return copyRemove(pos); + } + + @Override public Value reverse() { - final int n = (int) size; - final Item[] items = new Item[n]; - for(int i = 0; i < n; i++) items[n - 1 - i] = itemAt(i); - return Seq.get(items); + final ValueBuilder vb = new ValueBuilder(); + for(long i = 0; i < size; i++) vb.addFront(sub.itemAt(start + i)); + return vb.value(); } @Override @@ -93,17 +89,15 @@ @Override public Value materialize(final InputInfo ii) throws QueryException { - final int s = (int) size; - final ValueBuilder vb = new ValueBuilder(s); - for(int i = 0; i < s; i++) vb.add(itemAt(i).materialize(ii)); + final ValueBuilder vb = new ValueBuilder(); + for(long i = 0; i < size; i++) vb.add(itemAt(i).materialize(ii)); return vb.value(); } @Override public Value atomValue(final InputInfo ii) throws QueryException { - final int s = (int) size; - final ValueBuilder vb = new ValueBuilder(s); - for(int i = 0; i < s; i++) vb.add(itemAt(i).atomValue(ii)); + final ValueBuilder vb = new ValueBuilder(); + for(long i = 0; i < size; i++) vb.add(itemAt(i).atomValue(ii)); return vb.value(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/tree/BigSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/tree/BigSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/tree/BigSeq.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/tree/BigSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,572 @@ +package org.basex.query.value.seq.tree; + +import java.util.*; + +import org.basex.query.iter.*; +import org.basex.query.util.fingertree.*; +import org.basex.query.value.*; +import org.basex.query.value.item.*; +import org.basex.query.value.seq.*; +import org.basex.query.value.type.*; + +/** + * A sequence containing more elements than fit into a {@link SmallSeq}. + * + * @author BaseX Team 2005-15, BSD License + * @author Leo Woerteler + */ +final class BigSeq extends TreeSeq { + /** Left digit. */ + final Item[] left; + /** Middle tree. */ + final FingerTree middle; + /** Right digit. */ + final Item[] right; + + /** + * Constructor. + * @param left left digit + * @param middle middle tree + * @param right right digit + * @param ret type of all items in this sequence, can be {@code null} + */ + BigSeq(final Item[] left, final FingerTree middle, final Item[] right, + final Type ret) { + super(left.length + middle.size() + right.length, ret); + this.left = left; + this.middle = middle; + this.right = right; + assert left.length >= MIN_DIGIT && left.length <= MAX_DIGIT + && right.length >= MIN_DIGIT && right.length <= MAX_DIGIT; + } + + @Override + public Item itemAt(final long index) { + // index out of range? + if(index < 0) throw new IndexOutOfBoundsException("Index < 0: " + index); + if(index >= size) throw new IndexOutOfBoundsException(index + " >= " + size); + + // index in one of the digits? + if(index < left.length) return left[(int) index]; + final long midSize = size - right.length; + if(index >= midSize) return right[(int) (index - midSize)]; + + // the element is in the middle tree + return middle.get(index - left.length); + } + + @Override + public TreeSeq reverse() { + final int l = left.length, r = right.length; + final Item[] newLeft = new Item[r], newRight = new Item[l]; + for(int i = 0; i < r; i++) newLeft[i] = right[r - 1 - i]; + for(int i = 0; i < l; i++) newRight[i] = left[l - 1 - i]; + return new BigSeq(newLeft, middle.reverse(), newRight, ret); + } + + @Override + public TreeSeq insert(final long pos, final Item val) { + if(pos < 0) throw new IndexOutOfBoundsException("negative index: " + pos); + if(pos > size()) throw new IndexOutOfBoundsException("position too big: " + pos); + + final int l = left.length; + if(pos <= l) { + final int p = (int) pos; + final Item[] temp = slice(left, 0, l + 1); + System.arraycopy(temp, p, temp, p + 1, l - p); + temp[p] = val; + if(l < MAX_DIGIT) return new BigSeq(temp, middle, right, null); + + final int m = (l + 1) / 2; + return new BigSeq(slice(temp, 0, m), + middle.cons(new LeafNode(slice(temp, m, l + 1))), right, null); + } + + final long midSize = middle.size(); + if(pos - l < midSize) return new BigSeq(left, middle.insert(pos - l, val), right, null); + + final int r = right.length; + final int p = (int) (pos - l - midSize); + final Item[] temp = slice(right, 0, r + 1); + System.arraycopy(temp, p, temp, p + 1, r - p); + temp[p] = val; + if(r < MAX_DIGIT) return new BigSeq(left, middle, temp, null); + + final int m = (r + 1) / 2; + return new BigSeq(left, middle.snoc(new LeafNode(slice(temp, 0, m))), + slice(temp, m, r + 1), null); + } + + @Override + public TreeSeq remove(final long pos) { + if(pos < 0) throw new IndexOutOfBoundsException("negative index: " + pos); + if(pos >= size()) throw new IndexOutOfBoundsException("position too big: " + pos); + + if(pos < left.length) { + // delete from left digit + final int p = (int) pos, l = left.length; + if(l > MIN_DIGIT) { + // there is enough space, just delete the element + final Item[] newLeft = new Item[l - 1]; + System.arraycopy(left, 0, newLeft, 0, p); + System.arraycopy(left, p + 1, newLeft, p, newLeft.length - p); + return new BigSeq(newLeft, middle, right, ret); + } + + if(middle.isEmpty()) { + // merge left and right digit + final int r = right.length, n = l - 1 + r; + final Item[] vals = new Item[n]; + System.arraycopy(left, 0, vals, 0, p); + System.arraycopy(left, p + 1, vals, p, l - 1 - p); + System.arraycopy(right, 0, vals, l - 1, r); + return fromMerged(vals, ret); + } + + // extract a new left digit from the middle + final Item[] head = ((LeafNode) middle.head()).values; + final int r = head.length, n = l - 1 + r; + + if(r > MIN_LEAF) { + // refill from neighbor + final int move = (r - MIN_LEAF + 1) / 2; + final Item[] newLeft = new Item[l - 1 + move]; + System.arraycopy(left, 0, newLeft, 0, p); + System.arraycopy(left, p + 1, newLeft, p, l - 1 - p); + System.arraycopy(head, 0, newLeft, l - 1, move); + final Item[] newHead = slice(head, move, r); + return new BigSeq(newLeft, middle.replaceHead(new LeafNode(newHead)), right, ret); + } + + // merge digit and head node + final Item[] newLeft = new Item[n]; + System.arraycopy(left, 0, newLeft, 0, p); + System.arraycopy(left, p + 1, newLeft, p, l - 1 - p); + System.arraycopy(head, 0, newLeft, l - 1, r); + return new BigSeq(newLeft, middle.tail(), right, ret); + } + + final long midSize = middle.size(), rightOffset = left.length + midSize; + if(pos >= rightOffset) { + // delete from right digit + final int p = (int) (pos - rightOffset), r = right.length; + if(r > MIN_DIGIT) { + // there is enough space, just delete the element + final Item[] newRight = new Item[r - 1]; + System.arraycopy(right, 0, newRight, 0, p); + System.arraycopy(right, p + 1, newRight, p, r - 1 - p); + return new BigSeq(left, middle, newRight, ret); + } + + if(middle.isEmpty()) { + // merge left and right digit + final int l = left.length, n = l + r - 1; + final Item[] vals = new Item[n]; + System.arraycopy(left, 0, vals, 0, l); + System.arraycopy(right, 0, vals, l, p); + System.arraycopy(right, p + 1, vals, l + p, r - 1 - p); + return fromMerged(vals, ret); + } + + // extract a new right digit from the middle + final Item[] last = ((LeafNode) middle.last()).values; + final int l = last.length, n = l + r - 1; + + if(l > MIN_LEAF) { + // refill from neighbor + final int move = (l - MIN_LEAF + 1) / 2; + final Item[] newLast = slice(last, 0, l - move); + final Item[] newRight = new Item[r - 1 + move]; + System.arraycopy(last, l - move, newRight, 0, move); + System.arraycopy(right, 0, newRight, move, p); + System.arraycopy(right, p + 1, newRight, move + p, r - 1 - p); + return new BigSeq(left, middle.replaceLast(new LeafNode(newLast)), newRight, ret); + } + + // merge last node and digit + final Item[] newRight = new Item[n]; + System.arraycopy(last, 0, newRight, 0, l); + System.arraycopy(right, 0, newRight, l, p); + System.arraycopy(right, p + 1, newRight, l + p, r - 1 - p); + return new BigSeq(left, middle.init(), newRight, ret); + } + + // delete in middle tree + final TreeSlice slice = middle.remove(pos - left.length); + + if(slice.isTree()) { + // middle tree did not underflow + return new BigSeq(left, slice.getTree(), right, ret); + } + + // tree height might change + final Item[] mid = ((PartialLeafNode) slice.getPartial()).elems; + final int l = left.length, m = mid.length, r = right.length; + + if(l > r) { + // steal from the bigger digit, in this case left (cannot be minimal) + final int move = (l - MIN_DIGIT + 1) / 2; + final Item[] newLeft = slice(left, 0, l - move); + final Item[] newMid = slice(left, l - move, l + m); + System.arraycopy(mid, 0, newMid, move, m); + return new BigSeq(newLeft, FingerTree.singleton(new LeafNode(newMid)), right, ret); + } + + if(r > MIN_DIGIT) { + // steal from right digit + final int move = (r - MIN_DIGIT + 1) / 2; + final Item[] newMid = slice(mid, 0, m + move); + System.arraycopy(right, 0, newMid, m, move); + final Item[] newRight = slice(right, move, r); + return new BigSeq(left, FingerTree.singleton(new LeafNode(newMid)), newRight, ret); + } + + // divide onto left and right digit + final int ml = m / 2, mr = m - ml; + final Item[] newLeft = slice(left, 0, l + ml); + System.arraycopy(mid, 0, newLeft, l, ml); + final Item[] newRight = slice(right, -mr, r); + System.arraycopy(mid, ml, newRight, 0, mr); + final Type rt = ret; + return new BigSeq(newLeft, FingerTree.empty(), newRight, rt); + } + + @Override + public Value subSeq(final long pos, final long len) { + if(pos < 0) throw new IndexOutOfBoundsException("first index < 0: " + pos); + if(len < 0) throw new IndexOutOfBoundsException("length < 0: " + len); + final long midSize = middle.size(); + if(len > size - pos) + throw new IndexOutOfBoundsException("end out of bounds: " + (pos + len) + " > " + size); + + // the easy cases + if(len == 0) return Empty.SEQ; + if(len == 1) return itemAt(pos); + if(len == size) return this; + + final long end = pos + len; + if(end <= left.length) { + // completely in left digit + final int p = (int) pos, n = (int) len; + if(len <= MAX_SMALL) return new SmallSeq(slice(left, p, p + n), ret); + final int mid = p + n / 2; + final Type rt = ret; + return new BigSeq(slice(left, p, mid), FingerTree.empty(), slice(left, mid, p + n), rt); + } + + final long rightOffset = left.length + midSize; + if(pos >= rightOffset) { + // completely in right digit + final int p = (int) (pos - rightOffset), n = (int) len; + if(len <= MAX_SMALL) return new SmallSeq(slice(right, p, p + n), ret); + final int mid = p + n / 2; + final Type rt = ret; + return new BigSeq(slice(right, p, mid), FingerTree.empty(), + slice(right, mid, p + n), rt); + } + + final int inLeft = pos < left.length ? (int) (left.length - pos) : 0, + inRight = end > rightOffset ? (int) (end - rightOffset) : 0; + if(inLeft >= MIN_DIGIT && inRight >= MIN_DIGIT) { + // digits are still long enough + final Item[] newLeft = inLeft == left.length ? left : slice(left, (int) pos, left.length); + final Item[] newRight = inRight == right.length ? right : slice(right, 0, inRight); + return new BigSeq(newLeft, middle, newRight, ret); + } + + if(middle.isEmpty()) { + // merge left and right partial digits + final Item[] out; + if(inLeft == 0) { + out = inRight == right.length ? right : slice(right, 0, inRight); + } else if(inRight == 0) { + out = inLeft == left.length ? left : slice(left, left.length - inLeft, left.length); + } else { + out = slice(left, left.length - inLeft, left.length + inRight); + System.arraycopy(right, 0, out, inLeft, inRight); + } + return fromMerged(out, ret); + } + + final long inMiddle = len - inLeft - inRight; + final FingerTree mid; + if(inMiddle == midSize) { + mid = middle; + } else { + // the middle tree must be split + final long off = pos < left.length ? 0 : pos - left.length; + final TreeSlice slice = middle.slice(off, inMiddle); + // only a partial leaf, merge with digits + if(!slice.isTree()) { + final Item[] single = ((PartialLeafNode) slice.getPartial()).elems; + if(inLeft > 0) { + final Item[] out = slice(left, (int) pos, left.length + single.length); + System.arraycopy(single, 0, out, inLeft, single.length); + return fromMerged(out, ret); + } + if(inRight > 0) { + final Item[] out = slice(single, 0, single.length + inRight); + System.arraycopy(right, 0, out, single.length, inRight); + return fromMerged(out, ret); + } + return new SmallSeq(single, ret); + } + + mid = slice.getTree(); + } + + // `mid` is non-empty + + // create a left digit + final int off = left.length - inLeft; + final Item[] newLeft; + final FingerTree mid1; + if(inLeft >= MIN_DIGIT) { + newLeft = inLeft == left.length ? left : slice(left, off, left.length); + mid1 = mid; + } else { + final Item[] head = ((LeafNode) mid.head()).values; + if(inLeft == 0) { + newLeft = head; + } else { + newLeft = slice(head, -inLeft, head.length); + System.arraycopy(left, off, newLeft, 0, inLeft); + } + mid1 = mid.tail(); + } + + // create a right digit + final Item[] newRight; + final FingerTree newMiddle; + if(inRight >= MIN_DIGIT) { + newMiddle = mid1; + newRight = inRight == right.length ? right : slice(right, 0, inRight); + } else if(!mid1.isEmpty()) { + final Item[] last = ((LeafNode) mid1.last()).values; + newMiddle = mid1.init(); + if(inRight == 0) { + newRight = last; + } else { + newRight = slice(last, 0, last.length + inRight); + System.arraycopy(right, 0, newRight, last.length, inRight); + } + } else { + // not enough elements for a right digit + if(inRight == 0) return fromMerged(newLeft, ret); + final int n = newLeft.length + inRight; + final Item[] out = slice(newLeft, 0, n); + System.arraycopy(right, 0, out, newLeft.length, inRight); + return fromMerged(out, ret); + } + + return new BigSeq(newLeft, newMiddle, newRight, ret); + } + + /** + * Creates a sequence from two merged, possibly partial digits. + * This method requires that the input array's length is not longer than {@code 2 * MAX_DIGIT}. + * @param merged the merged digits + * @param rt element type + * @return the array + */ + private TreeSeq fromMerged(final Item[] merged, final Type rt) { + if(merged.length <= MAX_SMALL) return new SmallSeq(merged, rt); + final int mid = merged.length / 2; + return new BigSeq(slice(merged, 0, mid), FingerTree.empty(), + slice(merged, mid, merged.length), rt); + } + + @Override + public TreeSeq concat(final TreeSeq seq) { + final Type retType = type == seq.type ? type : null; + if(seq instanceof SmallSeq) { + // merge with right digit + final Item[] newRight = concat(right, ((SmallSeq) seq).elems); + final int r = newRight.length; + if(r <= MAX_DIGIT) return new BigSeq(left, middle, newRight, retType); + final int mid = r / 2; + final Item[] leaf = slice(newRight, 0, mid); + final FingerTree newMid = middle.snoc(new LeafNode(leaf)); + return new BigSeq(left, newMid, slice(newRight, mid, r), retType); + } + + final BigSeq bigOther = (BigSeq) seq; + + // make nodes out of the digits facing each other + final Item[] ls = right, rs = bigOther.left; + final int l = ls.length, n = l + rs.length; + final int k = (n + MAX_LEAF - 1) / MAX_LEAF, s = (n + k - 1) / k; + @SuppressWarnings("unchecked") + final Node[] midNodes = new Node[k]; + int p = 0; + for(int i = 0; i < k; i++) { + final int curr = Math.min(n - p, s); + final Item[] arr = new Item[curr]; + for(int j = 0; j < curr; j++, p++) arr[j] = p < l ? ls[p] : rs[p - l]; + midNodes[i] = new LeafNode(arr); + } + + return new BigSeq(left, middle.concat(midNodes, n, bigOther.middle), bigOther.right, retType); + } + + @Override + public ListIterator iterator(final long start) { + final Item[] ls = left, rs = right; + final int l = ls.length , r = rs.length, startPos; + final long m = middle.size(); + final ListIterator sub; + if(start < l) { + startPos = (int) start - l; + sub = middle.listIterator(0); + } else if(start - l < m) { + startPos = 0; + sub = middle.listIterator(start - l); + } else { + startPos = (int) (start - l - m) + 1; + sub = middle.listIterator(m); + } + + return new ListIterator() { + int pos = startPos; + + @Override + public int nextIndex() { + return pos < 0 ? l + pos + : pos > 0 ? (int) (l + m + pos - 1) + : l + sub.nextIndex(); + } + + @Override + public boolean hasNext() { + return pos <= r; + } + + @Override + public Item next() { + if(pos > r) throw new NoSuchElementException(); + if(pos < 0) { + // in left digit + return ls[l + pos++]; + } + + if(pos == 0) { + // in middle tree + if(sub.hasNext()) return sub.next(); + pos = 1; + } + + // in right digit + return rs[pos++ - 1]; + } + + @Override + public int previousIndex() { + return pos < 0 ? l + pos - 1 + : pos > 0 ? (int) (l + m + pos - 2) + : l + sub.previousIndex(); + } + + @Override + public boolean hasPrevious() { + return pos > -l; + } + + @Override + public Item previous() { + if(pos <= -l) throw new NoSuchElementException(); + if(pos > 0) { + // in right digit + if(--pos > 0) return rs[pos - 1]; + } + + if(pos == 0) { + // in middle tree + if(sub.hasPrevious()) return sub.previous(); + pos = -1; + return ls[l - 1]; + } + + // in left digit + return ls[l + --pos]; + } + + @Override + public void add(final Item e) { + throw new UnsupportedOperationException(); + } + + @Override + public void set(final Item e) { + throw new UnsupportedOperationException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public ValueIter iter() { + return new ValueIter() { + private long pos; + private Iterator sub; + + @Override + public Item next() { + if(pos >= size) return null; + final long p = pos++; + if(p < left.length) return left[(int) p]; + final long r = size - right.length; + if(p >= r) return right[(int) (p - r)]; + if(sub == null) sub = middle.iterator(); + return sub.next(); + } + + @Override + public Item get(final long i) { + return BigSeq.this.itemAt(i); + } + + @Override + public long size() { + return size; + } + + @Override + public Value value() { + return BigSeq.this; + } + }; + } + + @Override + void checkInvariants() { + final int l = left.length, r = right.length; + if(l < MIN_DIGIT || l > MAX_DIGIT) throw new AssertionError("Left digit: " + l); + if(r < MIN_DIGIT || r > MAX_DIGIT) throw new AssertionError("Right digit: " + r); + middle.checkInvariants(); + } + + @Override + TreeSeq consSmall(final Item[] vals) { + final int a = vals.length, b = left.length, n = a + b; + if(n <= MAX_DIGIT) { + // no need to change the middle tree + return new BigSeq(concat(vals, left), middle, right, null); + } + + if(a >= MIN_DIGIT && MIN_LEAF <= b && b <= MAX_LEAF) { + // reuse the arrays + return new BigSeq(vals, middle.cons(new LeafNode(left)), right, null); + } + + // left digit is too big + final int mid = n / 2, move = mid - a; + final Item[] newLeft = slice(vals, 0, mid); + System.arraycopy(left, 0, newLeft, a, move); + final LeafNode leaf = new LeafNode(slice(left, move, b)); + return new BigSeq(newLeft, middle.cons(leaf), right, null); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/tree/LeafNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/tree/LeafNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/tree/LeafNode.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/tree/LeafNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,241 @@ +package org.basex.query.value.seq.tree; + +import java.util.*; + +import org.basex.query.util.fingertree.*; +import org.basex.query.value.item.*; + +/** + * A leaf node containing {@link Item}s. + * + * @author BaseX Team 2005-15, BSD License + * @author Leo Woerteler + */ +final class LeafNode implements Node { + /** Elements stored in this leaf node. */ + final Item[] values; + + /** + * Constructor. + * @param values the values + */ + LeafNode(final Item[] values) { + this.values = values; + assert values.length >= TreeSeq.MIN_LEAF && values.length <= TreeSeq.MAX_LEAF; + } + + @Override + public long size() { + return values.length; + } + + @Override + public LeafNode reverse() { + final int n = values.length; + final Item[] out = new Item[n]; + for(int i = 0; i < n; i++) out[i] = values[n - 1 - i]; + return new LeafNode(out); + } + + @Override + public boolean insert(final Node[] siblings, final long pos, final Item val) { + final int p = (int) pos, n = values.length; + final Item[] vals = new Item[n + 1]; + System.arraycopy(values, 0, vals, 0, p); + vals[p] = val; + System.arraycopy(values, p, vals, p + 1, n - p); + + if(n < TreeSeq.MAX_LEAF) { + // there is capacity + siblings[1] = new LeafNode(vals); + return false; + } + + final LeafNode left = (LeafNode) siblings[0]; + if(left != null && left.values.length < TreeSeq.MAX_LEAF) { + // push elements to the left sibling + final Item[] lvals = left.values; + final int l = lvals.length, diff = TreeSeq.MAX_LEAF - l, move = (diff + 1) / 2; + final Item[] newLeft = new Item[l + move], newRight = new Item[n + 1 - move]; + System.arraycopy(lvals, 0, newLeft, 0, l); + System.arraycopy(vals, 0, newLeft, l, move); + System.arraycopy(vals, move, newRight, 0, newRight.length); + siblings[0] = new LeafNode(newLeft); + siblings[1] = new LeafNode(newRight); + return false; + } + + final LeafNode right = (LeafNode) siblings[2]; + if(right != null && right.values.length < TreeSeq.MAX_LEAF) { + // push elements to the right sibling + final Item[] rvals = right.values; + final int r = rvals.length, diff = TreeSeq.MAX_LEAF - r, move = (diff + 1) / 2, + l = n + 1 - move; + final Item[] newLeft = new Item[l], newRight = new Item[r + move]; + System.arraycopy(vals, 0, newLeft, 0, l); + System.arraycopy(vals, l, newRight, 0, move); + System.arraycopy(rvals, 0, newRight, move, r); + siblings[1] = new LeafNode(newLeft); + siblings[2] = new LeafNode(newRight); + return false; + } + + // split the node + final int l = vals.length / 2, r = vals.length - l; + final Item[] newLeft = new Item[l], newRight = new Item[r]; + System.arraycopy(vals, 0, newLeft, 0, l); + System.arraycopy(vals, l, newRight, 0, r); + siblings[3] = siblings[2]; + siblings[1] = new LeafNode(newLeft); + siblings[2] = new LeafNode(newRight); + return true; + } + + @Override + public NodeLike[] remove(final Node left, + final Node right, final long pos) { + final int p = (int) pos, n = values.length; + @SuppressWarnings("unchecked") + final NodeLike[] out = new NodeLike[] { left, null, right }; + if(n > TreeSeq.MIN_LEAF) { + // we do not have to split + final Item[] vals = new Item[n - 1]; + System.arraycopy(values, 0, vals, 0, p); + System.arraycopy(values, p + 1, vals, p, n - 1 - p); + out[1] = new LeafNode(vals); + return out; + } + + final LeafNode leftLeaf = (LeafNode) left; + if(leftLeaf != null && leftLeaf.arity() > TreeSeq.MIN_LEAF) { + // steal from the left neighbor + final Item[] lvals = leftLeaf.values; + final int l = lvals.length, diff = l - TreeSeq.MIN_LEAF, move = (diff + 1) / 2; + final int ll = l - move, rl = n - 1 + move; + final Item[] newLeft = new Item[ll], newRight = new Item[rl]; + + System.arraycopy(lvals, 0, newLeft, 0, ll); + System.arraycopy(lvals, ll, newRight, 0, move); + System.arraycopy(values, 0, newRight, move, p); + System.arraycopy(values, p + 1, newRight, move + p, n - 1 - p); + out[0] = new LeafNode(newLeft); + out[1] = new LeafNode(newRight); + return out; + } + + final LeafNode rightLeaf = (LeafNode) right; + if(rightLeaf != null && rightLeaf.arity() > TreeSeq.MIN_LEAF) { + // steal from the right neighbor + final Item[] rvals = rightLeaf.values; + final int r = rvals.length, diff = r - TreeSeq.MIN_LEAF, move = (diff + 1) / 2; + final int ll = n - 1 + move, rl = r - move; + final Item[] newLeft = new Item[ll], newRight = new Item[rl]; + + System.arraycopy(values, 0, newLeft, 0, p); + System.arraycopy(values, p + 1, newLeft, p, n - 1 - p); + System.arraycopy(rvals, 0, newLeft, n - 1, move); + System.arraycopy(rvals, move, newRight, 0, rl); + out[1] = new LeafNode(newLeft); + out[2] = new LeafNode(newRight); + return out; + } + + if(left != null) { + // merge with left neighbor + final Item[] lvals = ((LeafNode) left).values; + final int l = lvals.length, r = values.length; + final Item[] vals = new Item[l + r - 1]; + System.arraycopy(lvals, 0, vals, 0, l); + System.arraycopy(values, 0, vals, l, p); + System.arraycopy(values, p + 1, vals, l + p, r - 1 - p); + out[0] = new LeafNode(vals); + out[1] = null; + return out; + } + + if(right != null) { + // merge with right neighbor + final Item[] rvals = ((LeafNode) right).values; + final int l = values.length, r = rvals.length; + final Item[] vals = new Item[l - 1 + r]; + System.arraycopy(values, 0, vals, 0, p); + System.arraycopy(values, p + 1, vals, p, l - 1 - p); + System.arraycopy(rvals, 0, vals, l - 1, r); + out[1] = null; + out[2] = new LeafNode(vals); + return out; + } + + // underflow + final Item[] vals = new Item[n - 1]; + System.arraycopy(values, 0, vals, 0, p); + System.arraycopy(values, p + 1, vals, p, n - 1 - p); + out[1] = new PartialLeafNode(vals); + return out; + } + + @Override + public int append(final NodeLike[] nodes, final int pos) { + if(pos == 0) { + nodes[pos] = this; + return 1; + } + + final NodeLike left = nodes[pos - 1]; + if(!(left instanceof PartialLeafNode)) { + nodes[pos] = this; + return pos + 1; + } + + final Item[] ls = ((PartialLeafNode) left).elems, rs = values; + final int l = ls.length, r = rs.length, n = l + r; + if(n <= TreeSeq.MAX_LEAF) { + // merge into one node + final Item[] vals = new Item[n]; + System.arraycopy(ls, 0, vals, 0, l); + System.arraycopy(rs, 0, vals, l, r); + nodes[pos - 1] = new LeafNode(vals); + return pos; + } + + // split into two + final int ll = n / 2, rl = n - ll, move = r - rl; + final Item[] newLeft = new Item[ll], newRight = new Item[rl]; + System.arraycopy(ls, 0, newLeft, 0, l); + System.arraycopy(rs, 0, newLeft, l, move); + System.arraycopy(rs, move, newRight, 0, rl); + nodes[pos - 1] = new LeafNode(newLeft); + nodes[pos] = new LeafNode(newRight); + return pos + 1; + } + + @Override + public NodeLike slice(final long off, final long size) { + final int p = (int) off, n = (int) size; + final Item[] out = new Item[n]; + System.arraycopy(values, p, out, 0, n); + return n < TreeSeq.MIN_LEAF ? new PartialLeafNode(out) : new LeafNode(out); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + size() + ")" + Arrays.toString(values); + } + + @Override + public long checkInvariants() { + if(values.length < TreeSeq.MIN_LEAF || values.length > TreeSeq.MAX_LEAF) + throw new AssertionError("Wrong " + getClass().getSimpleName() + " size: " + values.length); + return values.length; + } + + @Override + public int arity() { + return values.length; + } + + @Override + public Item getSub(final int index) { + return values[index]; + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/tree/PartialLeafNode.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/tree/PartialLeafNode.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/tree/PartialLeafNode.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/tree/PartialLeafNode.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,68 @@ +package org.basex.query.value.seq.tree; + +import java.util.*; + +import org.basex.query.util.fingertree.*; +import org.basex.query.value.item.*; + +/** + * A partial leaf node containing fewer elements than required in a node. + * + * @author BaseX Team 2005-15, BSD License + * @author Leo Woerteler + */ +final class PartialLeafNode implements NodeLike { + /** The single element. */ + final Item[] elems; + + /** + * Constructor. + * @param elems the elements + */ + PartialLeafNode(final Item[] elems) { + this.elems = elems; + } + + @Override + public int append(final NodeLike[] nodes, final int pos) { + if(pos == 0) { + nodes[0] = this; + return 1; + } + + final NodeLike left = nodes[pos - 1]; + if(left instanceof PartialLeafNode) { + final Item[] ls = ((PartialLeafNode) left).elems, rs = elems; + final int l = ls.length, r = rs.length, n = l + r; + final Item[] vals = new Item[n]; + System.arraycopy(ls, 0, vals, 0, l); + System.arraycopy(rs, 0, vals, l, r); + nodes[pos - 1] = n < TreeSeq.MIN_LEAF ? new PartialLeafNode(vals) : new LeafNode(vals); + return pos; + } + + final Item[] ls = ((LeafNode) left).values, rs = elems; + final int l = ls.length, r = rs.length, n = l + r; + if(n <= TreeSeq.MAX_LEAF) { + final Item[] vals = new Item[n]; + System.arraycopy(ls, 0, vals, 0, l); + System.arraycopy(rs, 0, vals, l, r); + nodes[pos - 1] = new LeafNode(vals); + return pos; + } + + final int ll = n / 2, rl = n - ll, move = l - ll; + final Item[] newLeft = new Item[ll], newRight = new Item[rl]; + System.arraycopy(ls, 0, newLeft, 0, ll); + System.arraycopy(ls, ll, newRight, 0, move); + System.arraycopy(rs, 0, newRight, move, r); + nodes[pos - 1] = new LeafNode(newLeft); + nodes[pos] = new LeafNode(newRight); + return pos + 1; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(" + elems.length + ")" + Arrays.toString(elems); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/tree/SmallSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/tree/SmallSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/tree/SmallSeq.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/tree/SmallSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,203 @@ +package org.basex.query.value.seq.tree; + +import java.util.*; + +import org.basex.query.iter.*; +import org.basex.query.util.fingertree.*; +import org.basex.query.value.*; +import org.basex.query.value.item.*; +import org.basex.query.value.seq.*; +import org.basex.query.value.type.*; + +/** + * A small sequence that is represented as a single Java array. + * + * @author BaseX Team 2005-15, BSD License + * @author Leo Woerteler + */ +final class SmallSeq extends TreeSeq { + /** The elements. */ + final Item[] elems; + + /** + * Constructor. + * @param elems elements + * @param ret type of all elements in this sequence + */ + SmallSeq(final Item[] elems, final Type ret) { + super(elems.length, ret); + this.elems = elems; + assert elems.length >= 2 && elems.length <= MAX_SMALL; + } + + @Override + public Item itemAt(final long index) { + // index to small? + if(index < 0) throw new IndexOutOfBoundsException("Index < 0: " + index); + + // index too big? + if(index >= elems.length) throw new IndexOutOfBoundsException(index + " >= " + elems.length); + + return elems[(int) index]; + } + + @Override + public TreeSeq reverse() { + final int n = elems.length; + final Item[] es = new Item[n]; + for(int i = 0; i < n; i++) es[i] = elems[n - 1 - i]; + return new SmallSeq(es, ret); + } + + @Override + public TreeSeq insert(final long pos, final Item val) { + if(pos < 0) throw new IndexOutOfBoundsException("negative index: " + pos); + if(pos > elems.length) throw new IndexOutOfBoundsException("position too big: " + pos); + + final int p = (int) pos, n = elems.length; + final Item[] out = new Item[n + 1]; + System.arraycopy(elems, 0, out, 0, p); + out[p] = val; + System.arraycopy(elems, p, out, p + 1, n - p); + + if(n < MAX_SMALL) return new SmallSeq(out, null); + return new BigSeq(slice(out, 0, MIN_DIGIT), FingerTree.empty(), + slice(out, MIN_DIGIT, n + 1), null); + } + + @Override + public Value remove(final long pos) { + if(pos < 0) throw new IndexOutOfBoundsException("negative index: " + pos); + if(pos >= elems.length) throw new IndexOutOfBoundsException("position too big: " + pos); + final int p = (int) pos, n = elems.length; + if(n == 2) return elems[pos == 0 ? 1 : 0]; + + final Item[] out = new Item[n - 1]; + System.arraycopy(elems, 0, out, 0, p); + System.arraycopy(elems, p + 1, out, p, n - 1 - p); + return new SmallSeq(out, ret); + } + + @Override + public Value subSeq(final long pos, final long len) { + if(pos < 0) throw new IndexOutOfBoundsException("first index < 0: " + pos); + if(len < 0) throw new IndexOutOfBoundsException("length < 0: " + len); + if(pos + len > elems.length) + throw new IndexOutOfBoundsException("end out of bounds: " + + (pos + len) + " > " + elems.length); + + final int p = (int) pos, n = (int) len; + return n == 0 ? Empty.SEQ : n == 1 ? elems[p] : new SmallSeq(slice(elems, p, p + n), ret); + } + + @Override + public TreeSeq concat(final TreeSeq other) { + return other.consSmall(elems); + } + + @Override + public ListIterator iterator(final long start) { + return new ListIterator() { + private int index = (int) start; + + @Override + public int nextIndex() { + return index; + } + + @Override + public boolean hasNext() { + return index < elems.length; + } + + @Override + public Item next() { + if(index >= elems.length) throw new NoSuchElementException(); + return elems[index++]; + } + + @Override + public int previousIndex() { + return index - 1; + } + + @Override + public boolean hasPrevious() { + return index > 0; + } + + @Override + public Item previous() { + if(index <= 0) throw new NoSuchElementException(); + return elems[--index]; + } + + @Override + public void set(final Item e) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(final Item e) { + throw new UnsupportedOperationException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public ValueIter iter() { + return new ValueIter() { + private int pos; + + @Override + public Item next() { + return pos < size ? elems[pos++] : null; + } + + @Override + public Item get(final long i) { + return elems[(int) i]; + } + + @Override + public long size() { + return size; + } + + @Override + public Value value() { + return SmallSeq.this; + } + }; + } + + @Override + void checkInvariants() { + final int n = elems.length; + if(n == 0) throw new AssertionError("Empty array in " + getClass().getSimpleName()); + if(n == 1) throw new AssertionError("Singleton array in " + getClass().getSimpleName()); + if(n > MAX_SMALL) throw new AssertionError("Array too big: " + n); + } + + @Override + TreeSeq consSmall(final Item[] left) { + final int l = left.length, r = elems.length, n = l + r; + if(Math.min(l, r) >= MIN_DIGIT) { + // both arrays can be used as digits + return new BigSeq(left, FingerTree.empty(), elems, null); + } + + final Item[] out = new Item[n]; + System.arraycopy(left, 0, out, 0, l); + System.arraycopy(elems, 0, out, l, r); + if(n <= MAX_SMALL) return new SmallSeq(out, null); + + final int mid = n / 2; + return new BigSeq(slice(out, 0, mid), FingerTree.empty(), slice(out, mid, n), null); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/tree/TreeSeqBuilder.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/tree/TreeSeqBuilder.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/tree/TreeSeqBuilder.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/tree/TreeSeqBuilder.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,292 @@ +package org.basex.query.value.seq.tree; + +import java.util.*; + +import org.basex.query.util.fingertree.*; +import org.basex.query.value.*; +import org.basex.query.value.item.*; +import org.basex.query.value.seq.*; +import org.basex.query.value.type.*; + +/** + * A builder for creating a {@link Seq}uence (with at least 2 items) by prepending and appending + * {@link Item}s. + * + * @author BaseX Team 2005-15, BSD License + * @author Leo Woerteler + */ +public final class TreeSeqBuilder implements Iterable { + /** Capacity of the root. */ + private static final int CAP = 2 * TreeSeq.MAX_DIGIT; + /** Size of inner nodes. */ + private static final int NODE_SIZE = (TreeSeq.MIN_LEAF + TreeSeq.MAX_LEAF + 1) / 2; + + /** Ring buffer containing the root-level elements. */ + private final Item[] vals = new Item[CAP]; + + /** Number of elements in left digit. */ + private int inLeft; + /** Middle between left and right digit in the buffer. */ + private int mid = CAP / 2; + /** Number of elements in right digit. */ + private int inRight; + /** Builder for the middle tree. */ + private final FingerTreeBuilder tree = new FingerTreeBuilder<>(); + + /** + * Returns a {@link Value} representation of the given items. + * @param items array containing the items + * @param n number of items (must be {@code 2} or more) + * @param type item type of the resulting value (not checked), may be {@code null} + * @return the value + */ + public static Seq value(final Item[] items, final int n, final Type type) { + if(n < 2) throw new AssertionError("At least 2 items expected"); + + if(n <= TreeSeq.MAX_SMALL) { + final Item[] small = new Item[n]; + System.arraycopy(items, 0, small, 0, n); + return new SmallSeq(small, type); + } + + final TreeSeqBuilder tsb = new TreeSeqBuilder(); + for(int i = 0; i < n; i++) tsb.add(items[i]); + return tsb.seq(type); + } + + /** + * Adds an element to the start of the array. + * @param elem element to add + * @return reference to this builder for convenience + */ + public TreeSeqBuilder addFront(final Item elem) { + if(inLeft < TreeSeq.MAX_DIGIT) { + // just insert the element + vals[(mid - inLeft + CAP - 1) % CAP] = elem; + inLeft++; + } else if(tree.isEmpty() && inRight < TreeSeq.MAX_DIGIT) { + // move the middle to the left + mid = (mid + CAP - 1) % CAP; + vals[(mid - inLeft + CAP) % CAP] = elem; + inRight++; + } else { + // push leaf node into the tree + tree.prepend(new LeafNode(items(mid - NODE_SIZE, NODE_SIZE))); + + // move rest of the nodes to the right + final int rest = inLeft - NODE_SIZE; + final int p0 = (mid - inLeft + CAP) % CAP; + for(int i = 0; i < rest; i++) { + final int from = (p0 + i) % CAP, to = (from + NODE_SIZE) % CAP; + vals[to] = vals[from]; + } + + // insert the element + vals[(mid - rest + CAP - 1) % CAP] = elem; + inLeft = rest + 1; + } + return this; + } + + /** + * Adds an element to the end of the array. + * @param elem element to add + * @return reference to this builder for convenience + */ + public TreeSeqBuilder add(final Item elem) { + if(inRight < TreeSeq.MAX_DIGIT) { + // just insert the element + vals[(mid + inRight) % CAP] = elem; + inRight++; + } else if(tree.isEmpty() && inLeft < TreeSeq.MAX_DIGIT) { + // move the middle to the right + mid = (mid + 1) % CAP; + vals[(mid + inRight + CAP - 1) % CAP] = elem; + inLeft++; + } else { + // push leaf node into the tree + tree.append(new LeafNode(items(mid, NODE_SIZE))); + + // move rest of the nodes to the right + final int rest = inRight - NODE_SIZE; + for(int i = 0; i < rest; i++) { + final int to = (mid + i) % CAP, from = (to + NODE_SIZE) % CAP; + vals[to] = vals[from]; + } + + // insert the element + vals[(mid + rest) % CAP] = elem; + inRight = rest + 1; + } + return this; + } + + /** + * Appends the items of the given value to this builder. + * @param val value to append + * @return this builder for convenience + */ + public TreeSeqBuilder add(final Value val) { + // shortcut for adding single items + if(val instanceof Item) return add((Item) val); + + if(!(val instanceof BigSeq)) { + for(final Item it : val) add(it); + return this; + } + + final BigSeq big = (BigSeq) val; + final Item[] ls = big.left, rs = big.right; + final FingerTree midTree = big.middle; + if(midTree.isEmpty()) { + for(final Item l : ls) add(l); + for(final Item r : rs) add(r); + return this; + } + + // merge middle digits + if(tree.isEmpty()) { + final int k = inLeft + inRight; + final Item[] temp = new Item[k]; + final int l = (mid - inLeft + CAP) % CAP, m = CAP - l; + if(k <= m) { + System.arraycopy(vals, l, temp, 0, k); + } else { + System.arraycopy(vals, l, temp, 0, m); + System.arraycopy(vals, 0, temp, m, k - m); + } + + inLeft = inRight = 0; + tree.append(midTree); + for(int i = ls.length; --i >= 0;) addFront(ls[i]); + for(int i = k; --i >= 0;) addFront(temp[i]); + for(int i = 0; i < rs.length; i++) add(rs[i]); + return this; + } + + final int inMiddle = inRight + ls.length, + leaves = (inMiddle + TreeSeq.MAX_LEAF - 1) / TreeSeq.MAX_LEAF, + leafSize = (inMiddle + leaves - 1) / leaves; + for(int i = 0, l = 0; l < leaves; l++) { + final int inLeaf = Math.min(leafSize, inMiddle - i); + final Item[] leaf = new Item[inLeaf]; + for(int p = 0; p < inLeaf; p++) { + leaf[p] = i < inRight ? vals[(mid + i) % CAP] : ls[i - inRight]; + i++; + } + tree.append(new LeafNode(leaf)); + } + + tree.append(midTree); + inRight = 0; + for(final Item r : rs) add(r); + return this; + } + + /** + * Creates a sequence containing the current elements of this builder. + * @return resulting sequence + */ + Seq seq() { + return seq((Type) null); + } + + /** + * Creates a sequence containing the current elements of this builder. + * @param ret type of all elements, may be {@code null} + * @return resulting sequence + */ + public Seq seq(final Type ret) { + final int n = inLeft + inRight; + final int start = (mid - inLeft + CAP) % CAP; + if(n < 2) throw new AssertionError("At least 2 items expected"); + + // small int array, fill directly + if(n <= TreeSeq.MAX_SMALL) return new SmallSeq(items(start, n), ret); + + // deep array + final int ll = tree.isEmpty() ? n / 2 : inLeft; + return new BigSeq(items(start, ll), tree.freeze(), items(start + ll, n - ll), ret); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('['); + if(tree.isEmpty()) { + final int n = inLeft + inRight, first = (mid - inLeft + CAP) % CAP; + if(n > 0) { + sb.append(vals[first]); + for(int i = 1; i < n; i++) sb.append(", ").append(vals[(first + i) % CAP]); + } + } else { + final int first = (mid - inLeft + CAP) % CAP; + sb.append(vals[first]); + for(int i = 1; i < inLeft; i++) sb.append(", ").append(vals[(first + i) % CAP]); + for(final Item item : tree) sb.append(", ").append(item); + for(int i = 0; i < inRight; i++) sb.append(", ").append(vals[(mid + i) % CAP]); + } + return sb.append(']').toString(); + } + + @Override + public Iterator iterator() { + + return new Iterator() { + private int pos = -inLeft; + private Iterator sub; + + @Override + public boolean hasNext() { + return pos <= inRight; + } + + @Override + public Item next() { + if(pos > inRight) throw new NoSuchElementException(); + + if(pos < 0) { + final int p = pos++; + return vals[(mid + p + CAP) % CAP]; + } + + if(pos == 0) { + if(tree != null) { + if(sub == null) sub = tree.iterator(); + if(sub.hasNext()) return sub.next(); + sub = null; + } + pos++; + } + + // pos > 0 + final int p = pos++; + return vals[(mid + p - 1) % CAP]; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Returns an array containing the given number of items starting at the given position in this + * builder's ring buffer. + * @param from position of the first item + * @param n number of items + * @return array containing the items + */ + private Item[] items(final int from, final int n) { + final Item[] arr = new Item[n]; + final int p = ((from % CAP) + CAP) % CAP; + final int m = CAP - p; + if(n <= m) { + System.arraycopy(vals, p, arr, 0, n); + } else { + System.arraycopy(vals, p, arr, 0, m); + System.arraycopy(vals, 0, arr, m, n - m); + } + return arr; + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/tree/TreeSeq.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/tree/TreeSeq.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/seq/tree/TreeSeq.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/seq/tree/TreeSeq.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,249 @@ +package org.basex.query.value.seq.tree; + +import static org.basex.query.QueryError.*; +import static org.basex.query.QueryText.*; + +import java.util.*; + +import org.basex.query.*; +import org.basex.query.iter.*; +import org.basex.query.value.*; +import org.basex.query.value.item.*; +import org.basex.query.value.node.*; +import org.basex.query.value.seq.*; +import org.basex.query.value.type.*; +import org.basex.query.value.type.SeqType.Occ; +import org.basex.util.*; + +/** + * A tree storing {@link Item}s. + * + * @author BaseX Team 2005-15, BSD License + * @author Leo Woerteler + */ +public abstract class TreeSeq extends Seq { + /** Minimum size of a leaf. */ + static final int MIN_LEAF = 8; + /** Maximum size of a leaf. */ + static final int MAX_LEAF = 2 * MIN_LEAF - 1; + /** Minimum number of elements in a digit. */ + static final int MIN_DIGIT = MIN_LEAF / 2; + /** Maximum number of elements in a digit. */ + static final int MAX_DIGIT = MAX_LEAF + MIN_DIGIT; + /** Maximum size of a small array. */ + static final int MAX_SMALL = 2 * MIN_DIGIT - 1; + + /** Item Types. */ + Type ret; + + /** + * Default constructor. + * @param size number of elements in this sequence + * @param ret type of all items in this sequence + */ + TreeSeq(final long size, final Type ret) { + super(size, ret == null ? AtomType.ITEM : ret); + this.ret = ret; + } + + @Override + public final Value insertBefore(final long pos, final Value val) { + final long n = val.size(); + if(n == 0) return this; + if(n == 1) return insert(pos, (Item) val); + + final long l = pos, r = size - pos; + if(val instanceof TreeSeq && (l == 0 || r == 0)) { + final TreeSeq other = (TreeSeq) val; + return l == 0 ? other.concat(this) : concat(other); + } + + final TreeSeqBuilder tsb = new TreeSeqBuilder(); + if(l < MAX_SMALL) { + tsb.add(val); + for(long i = l; --i >= 0;) tsb.addFront(itemAt(i)); + } else { + tsb.add(subSeq(0, l)); + tsb.add(val); + } + + if(r < MAX_SMALL) { + for(long i = size - r; i < size; i++) tsb.add(itemAt(i)); + } else { + tsb.add(subSeq(pos, r)); + } + + return tsb.seq(); + } + + /** + * Concatenates this sequence with another one. + * Running time: O(log (min { this.size(), other.size() })) + * @param other array to append to the end of this array + * @return resulting array + */ + public abstract TreeSeq concat(final TreeSeq other); + + /** + * Iterator over the members of this sequence. + * @param start starting position + * (i.e. the position initially returned by {@link ListIterator#nextIndex()}) + * @return array over the array members + */ + public abstract ListIterator iterator(final long start); + + @Override + public final Iterator iterator() { + return iterator(0); + } + + @Override + public final Object[] toJava() throws QueryException { + final ArrayList obj = new ArrayList<>((int) size); + for(final Item it : this) obj.add(it.toJava()); + return obj.toArray(); + } + + @Override + public final void plan(final FElem plan) { + final FElem el = planElem(SIZE, size); + addPlan(plan, el); + for(int v = 0; v != Math.min(size, 5); ++v) itemAt(v).plan(el); + } + + @Override + public final String toString() { + return toString(false); + } + + /** + * Returns a string representation of the sequence. + * @param error error flag + * @return string + */ + private String toString(final boolean error) { + final StringBuilder sb = new StringBuilder(PAREN1); + for(int i = 0; i < size; ++i) { + sb.append(i == 0 ? "" : SEP); + final Item it = itemAt(i); + sb.append(error ? it.toErrorString() : it.toString()); + if(sb.length() <= 16 || i + 1 == size) continue; + // output is chopped to prevent too long error strings + sb.append(SEP).append(DOTS); + break; + } + return sb.append(PAREN2).toString(); + } + + @Override + public abstract ValueIter iter(); + + @Override + public final Seq materialize(final InputInfo ii) throws QueryException { + final TreeSeqBuilder tsb = new TreeSeqBuilder(); + for(final Item it : this) tsb.add(it.materialize(ii)); + return tsb.seq(); + } + + @Override + public final Seq atomValue(final InputInfo ii) throws QueryException { + final TreeSeqBuilder tsb = new TreeSeqBuilder(); + for(final Item it : this) tsb.add(it.atomValue(ii)); + return tsb.seq(); + } + + @Override + public final long atomSize() { + long s = 0; + for(final Item it : this) s += it.atomSize(); + return s; + } + + @Override + public final int writeTo(final Item[] arr, final int off) { + final int n = (int) Math.min(arr.length - off, size()); + final Iterator iter = iterator(); + for(int i = 0; i < n; i++) arr[off + i] = iter.next(); + return n; + } + + @Override + public final boolean homogeneous() { + return ret != null && ret != AtomType.ITEM; + } + + @Override + public final boolean iterable() { + return false; + } + + @Override + public final Item ebv(final QueryContext qc, final InputInfo ii) throws QueryException { + final Item head = itemAt(0); + if(head instanceof ANode) return head; + throw EBV_X.get(ii, this); + } + + @Override + public final SeqType seqType() { + if(ret == null) { + final Iterator iter = iterator(); + Type t = iter.next().type; + while(iter.hasNext()) { + if(t != iter.next().type) { + t = AtomType.ITEM; + break; + } + } + ret = t; + type = t; + } + return SeqType.get(ret, Occ.ONE_MORE); + } + + /** + * Prepends the given elements to this sequence. + * @param vals values, with length at most {@link TreeSeq#MAX_SMALL} + * @return resulting sequence + */ + abstract TreeSeq consSmall(final Item[] vals); + + /** + * Returns items containing the values at the indices {@code from} to {@code to - 1} in + * the given sequence. Its length is always {@code to - from}. If {@code from} is smaller than + * zero, the first {@code -from} entries in the resulting sequence are {@code null}. + * If {@code to > arr.length} then the last {@code to - arr.length} entries are {@code null}. + * If {@code from == 0 && to == arr.length}, the original items are returned. + * @param items input sequence + * @param from first index, inclusive (may be negative) + * @param to last index, exclusive (may be greater than {@code arr.length}) + * @return resulting items + */ + static final Item[] slice(final Item[] items, final int from, final int to) { + final Item[] out = new Item[to - from]; + final int in0 = Math.max(0, from), in1 = Math.min(to, items.length); + final int out0 = Math.max(-from, 0); + System.arraycopy(items, in0, out, out0, in1 - in0); + return out; + } + + /** + * Concatenates the two item arrays. + * @param as first array + * @param bs second array + * @return resulting array + */ + static final Item[] concat(final Item[] as, final Item[] bs) { + final int l = as.length, r = bs.length, n = l + r; + final Item[] out = new Item[n]; + System.arraycopy(as, 0, out, 0, l); + System.arraycopy(bs, 0, out, l, r); + return out; + } + + /** + * Checks that this array's implementation does not violate any invariants. + * @throws AssertionError if an invariant was violated + */ + abstract void checkInvariants(); +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/type/ListType.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/type/ListType.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/type/ListType.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/type/ListType.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,7 +4,6 @@ import static org.basex.util.Token.*; import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.util.*; import org.basex.query.value.*; import org.basex.query.value.item.*; @@ -76,7 +75,7 @@ final InputInfo ii) throws QueryException { final byte[][] values = split(normalize(item.string(ii)), ' '); - final ValueBuilder vb = new ValueBuilder(values.length); + final ValueBuilder vb = new ValueBuilder(); for(final byte[] v : values) vb.add(type.cast(Str.get(v), qc, sc, ii)); return vb.value(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/type/SeqType.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/type/SeqType.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/type/SeqType.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/type/SeqType.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,7 +5,7 @@ import org.basex.query.*; import org.basex.query.expr.path.*; -import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.*; import org.basex.query.value.array.Array; import org.basex.query.value.item.*; @@ -397,8 +397,8 @@ if(val.isEmpty()) return Empty.SEQ; if(val instanceof Item) return cast((Item) val, qc, sc, ii, true); - final ValueBuilder vb = new ValueBuilder((int) vs); - for(int v = 0; v < vs; v++) vb.add(cast(val.itemAt(v), qc, sc, ii, true)); + final ValueBuilder vb = new ValueBuilder(); + for(final Item it : val) vb.add(cast(it, qc, sc, ii, true)); return vb.value(); } @@ -443,21 +443,21 @@ if(n == 0) return Empty.SEQ; - ValueBuilder vb = null; + ItemList buffer = null; for(int i = 0; i < n; i++) { final Item it = value.itemAt(i); if(instance(it, true)) { if(i == 0 && value.homogeneous()) return value; - if(vb != null) vb.add(it); + if(buffer != null) buffer.add(it); } else { - if(vb == null) { - vb = new ValueBuilder(new Item[n], 0); - for(int j = 0; j < i; j++) vb.add(value.itemAt(j)); + if(buffer == null) { + buffer = new ItemList(n); + for(int j = 0; j < i; j++) buffer.add(value.itemAt(j)); } - promote(qc, sc, ii, it, opt, vb); + promote(qc, sc, ii, it, opt, buffer); } } - return vb != null ? Seq.get(vb.items(), (int) vb.size(), type) : value; + return buffer != null ? buffer.value(type) : value; } /** @@ -467,32 +467,32 @@ * @param ii input info * @param item item to promote * @param opt if the result should be optimized - * @param vb value builder + * @param buffer value builder * @throws QueryException query exception */ public void promote(final QueryContext qc, final StaticContext sc, final InputInfo ii, - final Item item, final boolean opt, final ValueBuilder vb) throws QueryException { + final Item item, final boolean opt, final ItemList buffer) throws QueryException { if(type instanceof AtomType) { for(final Item atom : item.atomValue(ii)) { final Type tp = atom.type; if(tp.instanceOf(type)) { - vb.add(atom); + buffer.add(atom); } else if(tp == AtomType.ATM) { if(type.nsSensitive()) throw NSSENS_X_X.get(ii, item.type, type); - vb.add(type.cast(atom, qc, sc, ii)); + for(final Item it : type.cast(atom, qc, sc, ii)) buffer.add(it); } else if(type == AtomType.DBL && (tp == AtomType.FLT || tp.instanceOf(AtomType.DEC))) { - vb.add(Dbl.get(atom.dbl(ii))); + buffer.add(Dbl.get(atom.dbl(ii))); } else if(type == AtomType.FLT && tp.instanceOf(AtomType.DEC)) { - vb.add(Flt.get(atom.flt(ii))); + buffer.add(Flt.get(atom.flt(ii))); } else if(type == AtomType.STR && atom instanceof Uri) { - vb.add(Str.get(atom.string(ii))); + buffer.add(Str.get(atom.string(ii))); } else { throw INVPROMOTE_X_X_X.get(ii, item.seqType(), withOcc(Occ.ONE), item); } } } else if(item instanceof FItem && type instanceof FuncType) { - vb.add(((FItem) item).coerceTo((FuncType) type, qc, ii, opt)); + buffer.add(((FItem) item).coerceTo((FuncType) type, qc, ii, opt)); } else { throw INVPROMOTE_X_X_X.get(ii, item.seqType(), withOcc(Occ.ONE), item); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/ValueBuilder.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/ValueBuilder.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/ValueBuilder.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/ValueBuilder.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,126 @@ +package org.basex.query.value; + +import java.util.*; + +import org.basex.query.value.item.*; +import org.basex.query.value.seq.*; +import org.basex.query.value.seq.tree.*; +import org.basex.query.value.type.*; + +/** + * A builder for efficiently creating a {@link Value} by prepending and appending + * {@link Item}s and {@link Value}s. + * + * @author BaseX Team 2005-15, BSD License + * @author Leo Woerteler + */ +public final class ValueBuilder { + /** The first added value is cached. */ + private Value firstValue; + /** Underlying sequence builder, only instantiated if there are at least two items. */ + private TreeSeqBuilder builder; + + /** + * Concatenates two values. + * @param v1 first value to concatenate + * @param v2 second value to concatenate + * @return value which contains all items of {@code v1} followed by all items of {@code v2} + */ + public static Value concat(final Value v1, final Value v2) { + final long s1 = v1.size(); + if(s1 == 0) return v2; + final long s2 = v2.size(); + if(s2 == 0) return v1; + if(s1 > 1) return ((Seq) v1).insertBefore(s1, v2); + if(s2 > 1) return ((Seq) v2).insert(0, (Item) v1); + return TreeSeqBuilder.value(new Item[] { (Item) v1, (Item) v2 }, 2, null); + } + + /** + * Returns a {@link Value} representation of the given items. + * @param items array containing the items + * @param n number of items + * @param type item type of the resulting value (not checked), may be {@code null} + * @return the value + */ + public static Value value(final Item[] items, final int n, final Type type) { + return n == 0 ? Empty.SEQ : n == 1 ? items[0] : TreeSeqBuilder.value(items, n, type); + } + + /** + * Adds an item to the front of the built value. + * @param item item to add + * @return reference to this builder for convenience + */ + public ValueBuilder addFront(final Item item) { + final TreeSeqBuilder tree = builder; + if(tree != null) { + tree.addFront(item); + } else { + final Value first = firstValue; + if(first != null) { + builder = new TreeSeqBuilder().add(first).addFront(item); + firstValue = null; + } else { + firstValue = item; + } + } + return this; + } + + /** + * Appends a value to the end of the built value. + * @param value value to append + * @return reference to this builder for convenience + */ + public ValueBuilder add(final Value value) { + if(value.isEmpty()) return this; + + final TreeSeqBuilder tree = builder; + if(tree != null) { + tree.add(value); + } else { + final Value first = firstValue; + if(first != null) { + builder = new TreeSeqBuilder().add(first).add(value); + firstValue = null; + } else { + firstValue = value; + } + } + return this; + } + + /** + * Returns a {@link Value} representation of the items currently stored in this builder. + * @return contents of this builder + */ + public Value value() { + return value(null); + } + + /** + * Returns a {@link Value} representation of the items currently stored in this builder + * annotated with the given item type. + * @param type item type, may be {@code null} + * @return contents of this builder + */ + public Value value(final Type type) { + final Value first = firstValue; + if(first != null) return first; + final TreeSeqBuilder tree = builder; + return tree != null ? tree.seq(type) : Empty.SEQ; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('['); + final Iterator iter = firstValue != null ? firstValue.iterator() : + builder != null ? builder.iterator() : Collections.emptyIterator(); + if(iter.hasNext()) { + sb.append(iter.next()); + while(iter.hasNext()) sb.append(", ").append(iter.next()); + } + return sb.append(']').toString(); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/query/value/Value.java basex-8.2.3/basex-core/src/main/java/org/basex/query/value/Value.java --- basex-8.1.1/basex-core/src/main/java/org/basex/query/value/Value.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/query/value/Value.java 2015-07-14 10:54:40.000000000 +0000 @@ -13,6 +13,7 @@ import org.basex.query.expr.*; import org.basex.query.iter.*; import org.basex.query.util.*; +import org.basex.query.util.list.*; import org.basex.query.value.item.*; import org.basex.query.value.type.*; import org.basex.query.var.*; @@ -56,7 +57,7 @@ } @Override - public final Iterator iterator() { + public Iterator iterator() { return iter().iterator(); } @@ -72,6 +73,20 @@ } /** + * Returns a sub-sequence of this value with the given start and length.
    + * The following properties must hold: + *
      + *
    • {@code start >= 0}, + *
    • {@code len >= 0}, + *
    • {@code start + len <= size()} + *
    + * @param start starting position (zero-based) + * @param len number of items + * @return the sub-sequence + */ + public abstract Value subSeq(final long start, final long len); + + /** * Materializes streamable values, or returns a self reference. * @param ii input info * @return materialized item @@ -159,14 +174,14 @@ public abstract int writeTo(final Item[] arr, final int index); /** - * Creates an {@link ValueBuilder}, containing all items of this value. + * Creates an array containing all items of this value. * Use with care, as compressed Values are expanded, creating many objects. * @return cached items */ - public final ValueBuilder cache() { - final ValueBuilder vb = new ValueBuilder((int) size()); - vb.size(writeTo(vb.items(), 0)); - return vb; + public final ItemList cache() { + final long n = size(); + if(n > Integer.MAX_VALUE) throw Util.notExpected(n); + return new ItemList((int) n).add(this); } /** @@ -176,7 +191,7 @@ * @throws QueryIOException query I/O exception */ public final ArrayOutput serialize() throws QueryIOException { - return serialize(null); + return serialize((SerializerOptions) null); } /** @@ -188,9 +203,7 @@ public final ArrayOutput serialize(final SerializerOptions options) throws QueryIOException { final ArrayOutput ao = new ArrayOutput(); try { - try(final Serializer ser = Serializer.get(ao, options)) { - for(final Item it : this) ser.serialize(it); - } + serialize(Serializer.get(ao, options)); } catch(final QueryIOException ex) { throw ex; } catch(final IOException ex) { @@ -200,7 +213,20 @@ } /** + * Serializes the value with the specified serializer. + * @param ser serializer + * @throws IOException I/O exception + */ + public final void serialize(final Serializer ser) throws IOException { + for(final Item it : this) { + if(ser.finished()) break; + ser.serialize(it); + } + } + + /** * Gets the item at the given position in the value. + * The specified value must be lie within the valid bounds. * @param pos position * @return item */ @@ -212,6 +238,12 @@ */ public abstract boolean homogeneous(); + /** + * Returns a sequence in reverse order. + * @return sequence + */ + public abstract Value reverse(); + @Override public boolean accept(final ASTVisitor visitor) { final Data data = data(); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/server/ClientListener.java basex-8.2.3/basex-core/src/main/java/org/basex/server/ClientListener.java --- basex-8.1.1/basex-core/src/main/java/org/basex/server/ClientListener.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/server/ClientListener.java 2015-07-14 10:54:40.000000000 +0000 @@ -42,12 +42,6 @@ /** Socket reference. */ private final Socket socket; - /** Socket for events. */ - private Socket esocket; - /** Output for events. */ - private PrintOutput eout; - /** Flag for active events. */ - private boolean events; /** Input stream. */ private BufferInput in; /** Output stream. */ @@ -98,10 +92,6 @@ create(); } else if(sc == ServerCmd.ADD) { add(); - } else if(sc == ServerCmd.WATCH) { - watch(); - } else if(sc == ServerCmd.UNWATCH) { - unwatch(); } else if(sc == ServerCmd.REPLACE) { replace(); } else if(sc == ServerCmd.STORE) { @@ -247,11 +237,6 @@ try { new Close().run(context); socket.close(); - if(events) { - esocket.close(); - // remove this session from all events in pool - for(final Sessions s : context.events.values()) s.remove(this); - } } catch(final Throwable ex) { log(LogType.ERROR, Util.message(ex)); Util.stack(ex); @@ -267,33 +252,6 @@ } /** - * Registers the event socket. - * @param s socket - * @throws IOException I/O exception - */ - public synchronized void register(final Socket s) throws IOException { - esocket = s; - eout = PrintOutput.get(s.getOutputStream()); - eout.write(0); - eout.flush(); - } - - /** - * Sends a notification to the client. - * @param name event name - * @param msg event message - * @throws IOException I/O exception - */ - public synchronized void notify(final byte[] name, final byte[] msg) throws IOException { - last = System.currentTimeMillis(); - eout.print(name); - eout.write(0); - eout.print(msg); - eout.write(0); - eout.flush(); - } - - /** * Returns the host and port of a client. * @return string representation */ @@ -394,59 +352,6 @@ } /** - * Watches an event. - * @throws IOException I/O exception - */ - private void watch() throws IOException { - server.initEvents(); - - // initialize server-based event handling - if(!events) { - out.print(Integer.toString(context.soptions.get(StaticOptions.EVENTPORT))); - out.write(0); - out.print(Long.toString(getId())); - out.write(0); - out.flush(); - events = true; - } - final String name = in.readString(); - final Sessions session = context.events.get(name); - final boolean ok = session != null && !session.contains(this); - final String message; - if(ok) { - session.add(this); - message = WATCHING_EVENT_X; - } else if(session == null) { - message = EVENT_UNKNOWN_X; - } else { - message = EVENT_WATCHED_X; - } - info(Util.info(message, name), ok); - } - - /** - * Unwatches an event. - * @throws IOException I/O exception - */ - private void unwatch() throws IOException { - final String name = in.readString(); - - final Sessions session = context.events.get(name); - final boolean ok = session != null && session.contains(this); - final String message; - if(ok) { - session.remove(this); - message = UNWATCHING_EVENT_X; - } else if(session == null) { - message = EVENT_UNKNOWN_X; - } else { - message = EVENT_NOT_WATCHED_X; - } - info(Util.info(message, name), ok); - out.flush(); - } - - /** * Processes the query iterator. * @param sc server command * @throws IOException I/O exception diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/server/EventNotifier.java basex-8.2.3/basex-core/src/main/java/org/basex/server/EventNotifier.java --- basex-8.1.1/basex-core/src/main/java/org/basex/server/EventNotifier.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/server/EventNotifier.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -package org.basex.server; - -/** - * Notification interface for handling database events. - * - * @author BaseX Team 2005-15, BSD License - * @author Roman Raedle - */ -public interface EventNotifier { - /** - * Invoked when a database event was fired. - * @param value event string - */ - void notify(final String value); -} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/server/Log.java basex-8.2.3/basex-core/src/main/java/org/basex/server/Log.java --- basex-8.1.1/basex-core/src/main/java/org/basex/server/Log.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/server/Log.java 2015-07-14 10:54:40.000000000 +0000 @@ -97,7 +97,7 @@ * @param info info string (can be {@code null}) * @param perf performance string */ - private synchronized void write(final String address, final User user, final String type, + public synchronized void write(final String address, final User user, final String type, final String info, final Performance perf) { if(!sopts.get(StaticOptions.LOG)) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/server/ServerCmd.java basex-8.2.3/basex-core/src/main/java/org/basex/server/ServerCmd.java --- basex-8.1.1/basex-core/src/main/java/org/basex/server/ServerCmd.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/server/ServerCmd.java 2015-07-14 10:54:40.000000000 +0000 @@ -27,10 +27,6 @@ CREATE(8), /** Code for adding a document to a database: {path}0{input}0. */ ADD(9), - /** Code for watching an event: {name}0. */ - WATCH(10), - /** Code for unwatching an event: {name}0. */ - UNWATCH(11), /** Code for replacing a document in a database: {path}0{input}0. */ REPLACE(12), /** Code for storing raw data in a database: {path}0{input}0. */ diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/server/ServerQuery.java basex-8.2.3/basex-core/src/main/java/org/basex/server/ServerQuery.java --- basex-8.1.1/basex-core/src/main/java/org/basex/server/ServerQuery.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/server/ServerQuery.java 2015-07-14 10:54:40.000000000 +0000 @@ -30,8 +30,6 @@ /** Query processor. */ private QueryProcessor qp; - /** Serialization parameters. */ - private SerializerOptions parameters; /** Parsing flag. */ private boolean parsed; /** Query info. */ @@ -91,8 +89,7 @@ * @throws IOException I/O Exception */ public String parameters() throws IOException { - if(parameters == null) parameters = parse().qc.serParams(); - return parameters.toString(); + return parse().qc.serParams().toString(); } /** @@ -125,12 +122,11 @@ qi.compiling = perf.time(); final Iter ir = qp.iter(); qi.evaluating = perf.time(); - parameters(); // iterate through results int c = 0; final PrintOutput po = PrintOutput.get(encode ? new EncodingOutput(out) : out); - try(final Serializer ser = Serializer.get(po, full ? null : parameters)) { + try(final Serializer ser = Serializer.get(po, full ? null : qp.qc.serParams())) { for(Item it; (it = ir.next()) != null;) { if(iter) { po.write(full ? it.xdmInfo() : it.typeId().bytes()); diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/Array.java basex-8.2.3/basex-core/src/main/java/org/basex/util/Array.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/Array.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/Array.java 2015-07-14 10:54:40.000000000 +0000 @@ -132,7 +132,7 @@ } /** - * Sorts the specified tokens and returns an integer array with offsets to the sorted tokens. + * Sorts the specified tokens and returns an array with offsets to the sorted array. * @param values values to sort by * @param numeric numeric sort * @param ascending ascending @@ -146,7 +146,7 @@ } /** - * Sorts the specified doubles and returns an integer array with offsets to the sorted doubles. + * Sorts the specified double values and returns an array with offsets to the sorted array. * @param values values to sort by * @param ascending ascending * @return array containing the order @@ -158,7 +158,7 @@ } /** - * Sorts the specified integers and returns an integer array with offsets to the sorted integers. + * Sorts the specified int values and returns an array with offsets to the sorted array. * @param values values to sort by * @param ascending ascending * @return array containing the order @@ -167,6 +167,18 @@ final IntList il = number(values.length); il.sort(values, ascending); return il.finish(); + } + + /** + * Sorts the specified long values and returns an array with offsets to the sorted array. + * @param values values to sort by + * @param ascending ascending + * @return array containing the order + */ + public static int[] createOrder(final long[] values, final boolean ascending) { + final IntList il = number(values.length); + il.sort(values, ascending); + return il.finish(); } /** diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/Atts.java basex-8.2.3/basex-core/src/main/java/org/basex/util/Atts.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/Atts.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/Atts.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,16 +3,16 @@ import org.basex.util.list.*; /** - * This is a simple container for attributes (name/value pairs). + * Resizable-array implementation for attributes (name/value pairs). * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ public final class Atts extends ElementList { /** Name array. */ - private byte[][] nm = new byte[1][]; + private byte[][] nm; /** Value array. */ - private byte[][] vl = new byte[1][]; + private byte[][] vl; /** * Default constructor. diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/DateTime.java basex-8.2.3/basex-core/src/main/java/org/basex/util/DateTime.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/DateTime.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/DateTime.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,19 +4,16 @@ import java.util.*; /** - * This class contains static, thread-safe methods for parsing and formatting - * dates and times. + * This class contains static, thread-safe methods for parsing and formatting dates and times. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ public final class DateTime { - /** Date pattern. */ - public static final String PATTERN = "-\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}-\\d{2}"; + /** Date format without milliseconds and timestamp (uses UTC time zone). */ + public static final SimpleDateFormat DATETIME = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); /** Full date format. */ public static final SimpleDateFormat FULL = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - /** Date format without milliseconds and timestamp. */ - public static final SimpleDateFormat DATETIME = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); /** Date format. */ public static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd"); /** Time format. */ @@ -30,39 +27,36 @@ private DateTime() { } /** - * Parses the specified date and returns its time in milliseconds. - * Returns {@code null} if it cannot be converted. - * @param date date to be parsed - * @return time in milliseconds + * Parses a string and produces a date object. + * Returns the standard base time if it cannot be converted. + * @param date string representing a date + * @return parsed date */ - public static long parse(final String date) { + public static synchronized Date parse(final String date) { try { - synchronized(FULL) { return parse(date, FULL).getTime(); } + return (date.contains(":") ? FULL : DATETIME).parse(date); } catch(final ParseException ex) { Util.errln(ex); - return 0; + return new Date(0); } } /** - * Thread-safe method to create a string from a given date in a given format. - * @param format date format + * Creates a full string representation for the specified date. * @param date date * @return string with the formatted date */ - public static synchronized String format(final Date date, final DateFormat format) { - return format.format(date); + public static String format(final Date date) { + return FULL.format(date); } /** - * Thread-safe method to parse a date from a string in a given format. - * @param date string representing a date + * Creates a string representation for a date in the specified format. * @param format date format - * @return parsed date - * @throws ParseException if the string cannot be parsed + * @param date date + * @return string with the formatted date */ - public static synchronized Date parse(final String date, final DateFormat format) - throws ParseException { - return format.parse(date); + public static synchronized String format(final Date date, final DateFormat format) { + return format.format(date); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/FreeSlots.java basex-8.2.3/basex-core/src/main/java/org/basex/util/FreeSlots.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/FreeSlots.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/FreeSlots.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,15 +3,19 @@ import java.util.*; import java.util.Map.Entry; +import org.basex.util.list.*; + /** - * Organizes free slots for heap files. + * Organizes free slots in heap files. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ public final class FreeSlots { /** Free slots: byte sizes referencing file offsets. */ - private final TreeMap> free = new TreeMap<>(); + private final TreeMap free = new TreeMap<>(); + /** Number of slots. */ + private int slots; /** * Adds a value for the specified slot size. @@ -19,40 +23,105 @@ * @param offset file offset */ public void add(final int size, final long offset) { - LinkedList ll = free.get(size); - if(ll == null) { - ll = new LinkedList<>(); - free.put(size, ll); - } - ll.add(offset); + add(size, offset, true); } /** * Returns the offset of a slot that is greater than or equal to the specified size. - * @param size ideal byte size + * @param size ideal (minimum) slot size * @param offset offset used as fallback if no free slot is available * @return insertion offset */ public long get(final int size, final long offset) { - Long off = null; - final Entry> entry = free.ceilingEntry(size); + long off = -1; + final Entry entry = free.ceilingEntry(size); if(entry != null) { final int slotSize = entry.getKey(); - final LinkedList offsets = entry.getValue(); + if(slotSize < size) throw Util.notExpected("Free slot is too small: % < %", slotSize, size); + + final LongList offsets = entry.getValue(); off = offsets.pop(); + slots--; if(offsets.isEmpty()) free.remove(slotSize); - // add new slow entry if chosen entry is smaller than supplied size - if(slotSize < size) throw Util.notExpected("Free slot is too small: % < %", slotSize, size); - if(slotSize > size) add(size - slotSize, off + slotSize); + + if(slotSize > size) { + if(off + slotSize > offset) + throw Util.notExpected("Free slot exceeds file offset: % + % > %", off, slotSize, offset); + // chosen entry is smaller than supplied size: add entry for remaining free slot + add(slotSize - size, off + size); + } + } + return off == -1 ? offset : off; + } + + /** + * Adds a value for the specified slot size. + * @param size byte size + * @param offset file offset + * @param opt optimize + */ + private void add(final int size, final long offset, final boolean opt) { + LongList ll = free.get(size); + if(ll == null) { + ll = new LongList(); + free.put(size, ll); + } + ll.add(offset); + slots++; + if(opt) optimize(); + } + + /** + * Optimizes the free slot list structure by merging adjacent entries. + * Currently, this function is called after every addition of a new slot value. + */ + private void optimize() { + if(free.isEmpty()) return; + + // sort all entries by their offset (use native arrays; faster than TreeMap) + final int size = slots; + final LongList offList = new LongList(size); + final IntList sizeList = new IntList(size); + for(final Entry entry : free.entrySet()) { + final int slotSize = entry.getKey(); + final LongList list = entry.getValue(); + final int ll = list.size(); + for(int l = 0; l < ll; l++) { + offList.add(list.get(l)); + sizeList.add(slotSize); + } + } + if(size != offList.size()) + throw Util.notExpected("Wrong slot count: % vs. %", size, offList.size()); + + final long[] offsets = offList.finish(); + final int[] slotSizes = sizeList.finish(); + final int[] index = Array.createOrder(offsets, true); + + // rebuild map with merged slots + free.clear(); + slots = 0; + long offset = offsets[0]; + int slotSize = slotSizes[index[0]]; + for(int c = 1; c < size; c++) { + final long o = offsets[c]; + final int s = slotSizes[index[c]]; + if(o == offset + slotSize) { + slotSize += s; + } else { + add(slotSize, offset, false); + offset = o; + slotSize = s; + } } - return off == null ? offset : off; + add(slotSize, offset, false); } @Override public String toString() { - final StringBuilder sb = new StringBuilder(free.size() + " entries:\n"); - for(final Entry> entry : free.entrySet()) { - sb.append("- ").append(entry.getKey()).append(": ").append(entry.getValue()).append('\n'); + final StringBuilder sb = new StringBuilder("FREE SLOTS: " + free.size() + "\n"); + for(final Entry entry : free.entrySet()) { + sb.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append('\n'); } return sb.toString(); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/ft/GermanStemmer.java basex-8.2.3/basex-core/src/main/java/org/basex/util/ft/GermanStemmer.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/ft/GermanStemmer.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/ft/GermanStemmer.java 2015-07-14 10:54:40.000000000 +0000 @@ -131,7 +131,7 @@ strip(tb); } tl = tb.size(); - if(tb.get(tl - 1) == 'z') tb.set(tl - 1, (byte) 'x'); + if(tl > 0 && tb.get(tl - 1) == 'z') tb.set(tl - 1, (byte) 'x'); return tb; } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/hash/TokenSet.java basex-8.2.3/basex-core/src/main/java/org/basex/util/hash/TokenSet.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/hash/TokenSet.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/hash/TokenSet.java 2015-07-14 10:54:40.000000000 +0000 @@ -133,7 +133,7 @@ * The deletion of keys will lead to empty entries. If {@link #size} is called after * deletions, the original number of entries will be returned. * @param key key - * @return deleted key or 0 + * @return id of the deleted key, or {@code 0} if the key did not exist */ public int delete(final byte[] key) { final int b = Token.hash(key) & buckets.length - 1; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/http/HttpClient.java basex-8.2.3/basex-core/src/main/java/org/basex/util/http/HttpClient.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/http/HttpClient.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/http/HttpClient.java 2015-07-14 10:54:40.000000000 +0000 @@ -19,6 +19,7 @@ import org.basex.io.serial.*; import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.util.*; @@ -55,7 +56,7 @@ * @return HTTP response * @throws QueryException query exception */ - public ValueIter sendRequest(final byte[] href, final ANode request, final ValueBuilder bodies) + public Iter sendRequest(final byte[] href, final ANode request, final Iter bodies) throws QueryException { final HttpRequest req = new HttpRequestParser(info).parse(request, bodies); @@ -75,7 +76,7 @@ setRequestContent(conn.getOutputStream(), req); } - return new HttpResponse(info, options).getResponse(conn, body, mediaType); + return new HttpResponse(info, options).getResponse(conn, body, mediaType).iter(); } catch(final IOException ex) { throw HC_ERROR_X.get(info, ex); @@ -248,8 +249,8 @@ * @param out output stream * @throws IOException I/O exception */ - private static void writePayload(final ValueBuilder payload, - final HashMap atts, final OutputStream out) throws IOException { + private static void writePayload(final ItemList payload, final HashMap atts, + final OutputStream out) throws IOException { // detect method (specified by @method or derived from @media-type) String method = atts.get(SerializerOptions.METHOD.name()); @@ -278,8 +279,8 @@ if(Strings.eq(method, BINARY)) { out.write(io.read()); } else { - final ValueBuilder vb = new ValueBuilder().add(Str.get(new TextInput(io).content())); - write(vb, atts, method, out); + final ItemList buffer = new ItemList().add(Str.get(new TextInput(io).content())); + write(buffer, atts, method, out); } } } @@ -292,7 +293,7 @@ * @param out connection output stream * @throws IOException I/O Exception */ - private static void write(final ValueBuilder payload, final HashMap attrs, + private static void write(final ItemList payload, final HashMap attrs, final String method, final OutputStream out) throws IOException { // extract serialization parameters @@ -305,7 +306,7 @@ // serialize items according to the parameters try(final Serializer ser = Serializer.get(out, sopts)) { - payload.serialize(ser); + for(final Item it : payload) ser.serialize(it); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/http/HttpPayload.java basex-8.2.3/basex-core/src/main/java/org/basex/util/http/HttpPayload.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/http/HttpPayload.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/http/HttpPayload.java 2015-07-14 10:54:40.000000000 +0000 @@ -16,7 +16,6 @@ import org.basex.io.in.*; import org.basex.io.serial.*; import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.util.list.*; import org.basex.query.value.*; import org.basex.query.value.item.*; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/http/HttpRequest.java basex-8.2.3/basex-core/src/main/java/org/basex/util/http/HttpRequest.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/http/HttpRequest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/http/HttpRequest.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,7 +3,7 @@ import java.util.*; import org.basex.core.StaticOptions.AuthMethod; -import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.util.http.HttpText.*; /** @@ -20,7 +20,7 @@ /** Body or multipart attributes. */ public final HashMap payloadAttrs = new HashMap<>(); /** Body content. */ - public final ValueBuilder bodyContent = new ValueBuilder(); + public final ItemList bodyContent = new ItemList(); /** Parts in case of multipart request. */ public final ArrayList parts = new ArrayList<>(); /** Indicator for multipart request. */ @@ -49,6 +49,6 @@ /** Attributes of part body. */ public final HashMap bodyAttrs = new HashMap<>(); /** Content of part body. */ - public final ValueBuilder bodyContent = new ValueBuilder(); + public final ItemList bodyContent = new ItemList(); } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/http/HttpRequestParser.java basex-8.2.3/basex-core/src/main/java/org/basex/util/http/HttpRequestParser.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/http/HttpRequestParser.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/http/HttpRequestParser.java 2015-07-14 10:54:40.000000000 +0000 @@ -10,6 +10,7 @@ import org.basex.io.serial.*; import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.util.*; @@ -40,7 +41,7 @@ * @return parsed request * @throws QueryException query exception */ - public HttpRequest parse(final ANode request, final ValueBuilder bodies) throws QueryException { + public HttpRequest parse(final ANode request, final Iter bodies) throws QueryException { final HttpRequest req = new HttpRequest(); if(request != null) { @@ -94,7 +95,7 @@ * @param hdrs map for parsed headers * @return body or multipart */ - private static ANode parseHdrs(final AxisIter iter, final HashMap hdrs) { + private static ANode parseHdrs(final BasicNodeIter iter, final HashMap hdrs) { for(final ANode node : iter) { final QNm nm = node.qname(); if(nm == null) continue; @@ -122,7 +123,7 @@ * @throws QueryException query exception */ private void parseBody(final ANode body, final Item contItem, final HashMap attrs, - final ValueBuilder bodyContent) throws QueryException { + final ItemList bodyContent) throws QueryException { parseAttrs(body, attrs); checkBody(body, attrs); @@ -147,14 +148,14 @@ * @param parts list for multipart parts * @throws QueryException query exception */ - private void parseMultipart(final ANode multipart, final ValueBuilder contItems, + private void parseMultipart(final ANode multipart, final Iter contItems, final HashMap attrs, final ArrayList parts) throws QueryException { parseAttrs(multipart, attrs); if(attrs.get(SerializerOptions.MEDIA_TYPE.name()) == null) throw HC_REQ_X.get(info, "Attribute media-type of http:multipart is mandatory"); - final AxisIter prts = multipart.children(); + final BasicNodeIter prts = multipart.children(); while(true) { final Part p = new Part(); final ANode partBody = parseHdrs(prts, p.headers); @@ -224,7 +225,7 @@ // if src attribute is used to set the content of the body, no // other attributes must be specified and no content must be present - if(bodyAttrs.get(SRC) != null && (bodyAttrs.size() > 2 || body.children().more())) + if(bodyAttrs.get(SRC) != null && (bodyAttrs.size() > 2 || body.children().next() != null)) throw HC_ATTR.get(info); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/http/HttpResponse.java basex-8.2.3/basex-core/src/main/java/org/basex/util/http/HttpResponse.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/http/HttpResponse.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/http/HttpResponse.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,7 +8,7 @@ import org.basex.core.*; import org.basex.query.*; -import org.basex.query.iter.*; +import org.basex.query.util.list.*; import org.basex.query.value.node.*; import org.basex.util.*; @@ -45,24 +45,26 @@ * @throws QueryException query exception */ @SuppressWarnings("resource") - public ValueIter getResponse(final HttpURLConnection conn, final boolean body, final String mtype) + public ItemList getResponse(final HttpURLConnection conn, final boolean body, final String mtype) throws IOException, QueryException { // check content type - InputStream is = conn.getErrorStream(); - final boolean error = is != null; + boolean error = false; + InputStream is = null; try { - if(!error) is = conn.getInputStream(); + is = conn.getInputStream(); } catch(final IOException ex) { Util.debug(ex); + is = conn.getErrorStream(); + error = true; } // result - final ValueBuilder vb = new ValueBuilder(); + final ItemList res = new ItemList(); // construct final FElem response = new FElem(Q_RESPONSE).declareNS(); - vb.add(response); + res.add(response); final String msg = conn.getResponseMessage(); response.add(STATUS, token(conn.getResponseCode())); @@ -85,11 +87,11 @@ final MediaType type = error || mtype == null ? ctype == null ? MediaType.TEXT_PLAIN : new MediaType(ctype) : new MediaType(mtype); response.add(hp.parse(type)); - if(body) vb.add(hp.payloads()); + if(body) res.add(hp.payloads()); } finally { is.close(); } } - return vb; + return res; } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/http/HttpText.java basex-8.2.3/basex-core/src/main/java/org/basex/util/http/HttpText.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/http/HttpText.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/http/HttpText.java 2015-07-14 10:54:40.000000000 +0000 @@ -20,6 +20,8 @@ String AUTHORIZATION = "Authorization"; /** HTTP header: Content-Type. */ String CONTENT_TYPE = "Content-Type"; + /** HTTP header: Location. */ + String LOCATION = "Location"; /** HTTP header: Accept. */ String ACCEPT = "Accept"; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/list/BoolList.java basex-8.2.3/basex-core/src/main/java/org/basex/util/list/BoolList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/list/BoolList.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/list/BoolList.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,7 +5,7 @@ import org.basex.util.*; /** - * This is a simple container for native booleans. + * Resizable-array implementation for native booleans. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/list/ByteList.java basex-8.2.3/basex-core/src/main/java/org/basex/util/list/ByteList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/list/ByteList.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/list/ByteList.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,7 +7,7 @@ import org.basex.util.*; /** - * This is a simple container for byte values. + * Resizable-array implementation for native bytes. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/list/ElementList.java basex-8.2.3/basex-core/src/main/java/org/basex/util/list/ElementList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/list/ElementList.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/list/ElementList.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,7 +11,7 @@ */ public abstract class ElementList { /** Resize factor for extending the arrays. */ - double factor = Array.RESIZE; + protected double factor = Array.RESIZE; /** Number of elements. */ protected int size; diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/list/IntList.java basex-8.2.3/basex-core/src/main/java/org/basex/util/list/IntList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/list/IntList.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/list/IntList.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,7 +5,7 @@ import org.basex.util.*; /** - * This is a simple container for native integers. + * Resizable-array implementation for native int values. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen @@ -113,14 +113,14 @@ /** * Inserts elements at the specified index position. * @param index inserting position - * @param element elements to be inserted + * @param elements elements to be inserted */ - public final void insert(final int index, final int[] element) { - final int l = element.length; + public final void insert(final int index, final int... elements) { + final int l = elements.length; if(l == 0) return; if(size + l > list.length) list = Arrays.copyOf(list, newSize(size + l)); Array.move(list, index, l, size - index); - System.arraycopy(element, 0, list, index, l); + System.arraycopy(elements, 0, list, index, l); size += l; } @@ -284,12 +284,23 @@ } /** + * Sorts the data in the order of the specified numeric array. + * Note that the input array will be resorted as well. + * The algorithm is derived from {@link Arrays#sort(int[])}. + * @param num token array to sort by + * @param asc ascending + */ + public final void sort(final long[] num, final boolean asc) { + sort(0, size, asc, num); + } + + /** * Sorts the array. * @param s offset * @param e length * @param g numeric sort * @param f ascending/descending sort - * @param t sort tokens + * @param t sort array */ private void sort(final int s, final int e, final boolean g, final boolean f, final byte[][] t) { if(e < 7) { @@ -350,7 +361,7 @@ * @param s offset * @param e length * @param f ascending/descending sort - * @param t sort tokens + * @param t sort array */ private void sort(final int s, final int e, final boolean f, final double[] t) { if(e < 7) { @@ -411,7 +422,7 @@ * @param s offset * @param e length * @param f ascending/descending sort - * @param t sort tokens + * @param t sort array */ private void sort(final int s, final int e, final boolean f, final int[] t) { if(e < 7) { @@ -468,6 +479,67 @@ } /** + * Sorts the array. + * @param s offset + * @param e length + * @param f ascending/descending sort + * @param t sort array + */ + private void sort(final int s, final int e, final boolean f, final long[] t) { + if(e < 7) { + for(int i = s; i < e + s; ++i) { + for(int j = i; j > s; j--) { + final long h = t[j - 1] - t[j]; + if(f ? h < 0 : h > 0) break; + s(j, j - 1, t); + } + } + return; + } + + int m = s + (e >> 1); + if(e > 7) { + int l = s; + int n = s + e - 1; + if(e > 40) { + final int k = e >>> 3; + l = m(l, l + k, l + (k << 1)); + m = m(m - k, m, m + k); + n = m(n - (k << 1), n - k, n); + } + m = m(l, m, n); + } + final long v = t[m]; + + int a = s, b = a, c = s + e - 1, d = c; + while(true) { + while(b <= c) { + final long h = t[b] - v; + if(f ? h > 0 : h < 0) break; + if(h == 0) s(a++, b, t); + ++b; + } + while(c >= b) { + final long h = t[c] - v; + if(f ? h < 0 : h > 0) break; + if(h == 0) s(c, d--, t); + --c; + } + if(b > c) break; + s(b++, c--, t); + } + + final int n = s + e; + int k = Math.min(a - s, b - a); + s(s, b - k, k, t); + k = Math.min(d - c, n - d - 1); + s(b, n - k, k, t); + + if((k = b - a) > 1) sort(s, k, f, t); + if((k = d - c) > 1) sort(n - k, k, f, t); + } + + /** * Compares two numeric tokens and returns an integer. * @param a first token * @param b second token @@ -492,7 +564,7 @@ * Swaps two array elements. * @param a first offset * @param b second offset - * @param t sort tokens + * @param t sort array */ private void s(final int a, final int b, final byte[][] t) { final int l = list[a]; @@ -507,7 +579,7 @@ * Swaps two array elements. * @param a first offset * @param b second offset - * @param t sort tokens + * @param t sort array */ private void s(final int a, final int b, final double[] t) { final int l = list[a]; @@ -522,7 +594,7 @@ * Swaps two array elements. * @param a first offset * @param b second offset - * @param t sort tokens + * @param t sort array */ private void s(final int a, final int b, final int[] t) { final int l = list[a]; @@ -534,11 +606,26 @@ } /** + * Swaps two array elements. + * @param a first offset + * @param b second offset + * @param t sort array + */ + private void s(final int a, final int b, final long[] t) { + final int l = list[a]; + list[a] = list[b]; + list[b] = l; + final long c = t[a]; + t[a] = t[b]; + t[b] = c; + } + + /** * Swaps x[a .. (a+n-1)] with x[b .. (b+n-1)]. * @param a first offset * @param b second offset * @param n number of elements - * @param t sort tokens + * @param t sort array */ private void s(final int a, final int b, final int n, final byte[][] t) { for(int i = 0; i < n; ++i) s(a + i, b + i, t); @@ -549,7 +636,7 @@ * @param a first offset * @param b second offset * @param n number of elements - * @param t sort tokens + * @param t sort array */ private void s(final int a, final int b, final int n, final double[] t) { for(int i = 0; i < n; ++i) s(a + i, b + i, t); @@ -560,12 +647,23 @@ * @param a first offset * @param b second offset * @param n number of elements - * @param t sort tokens + * @param t sort array */ private void s(final int a, final int b, final int n, final int[] t) { for(int i = 0; i < n; ++i) s(a + i, b + i, t); } + /** + * Swaps x[a .. (a+n-1)] with x[b .. (b+n-1)]. + * @param a first offset + * @param b second offset + * @param n number of elements + * @param t sort array + */ + private void s(final int a, final int b, final int n, final long[] t) { + for(int i = 0; i < n; ++i) s(a + i, b + i, t); + } + /** * Returns the index of the median of the three indexed integers. * @param a first offset diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/list/LongList.java basex-8.2.3/basex-core/src/main/java/org/basex/util/list/LongList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/list/LongList.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/list/LongList.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,129 @@ +package org.basex.util.list; + +import java.util.*; + +import org.basex.util.*; + +/** + * Resizable-array implementation for native long values. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public class LongList extends ElementList { + /** Element container. */ + protected long[] list; + + /** + * Default constructor. + */ + public LongList() { + this(Array.CAPACITY); + } + + /** + * Constructor, specifying an initial internal array size. + * @param capacity initial array capacity + */ + public LongList(final int capacity) { + list = new long[capacity]; + } + + /** + * Adds an element to the array. + * @param element element to be added + * @return self reference + */ + public final LongList add(final long element) { + long[] lst = list; + final int s = size; + if(s == lst.length) lst = Arrays.copyOf(lst, newSize()); + lst[s] = element; + list = lst; + size = s + 1; + return this; + } + + /** + * Adds elements to the array. + * @param elements elements to be added + * @return self reference + */ + public final LongList add(final long... elements) { + long[] lst = list; + final int l = elements.length, s = size, ns = s + l; + if(ns > lst.length) lst = Arrays.copyOf(lst, newSize(ns)); + System.arraycopy(elements, 0, lst, s, l); + list = lst; + size = ns; + return this; + } + + /** + * Returns the element at the specified position. + * @param index index of the element to return + * @return element + */ + public final long get(final int index) { + return list[index]; + } + + /** + * Returns the uppermost element from the stack. + * @return the uppermost element + */ + public final long peek() { + return list[size - 1]; + } + + /** + * Pops the uppermost element from the stack. + * @return the popped element + */ + public final long pop() { + return list[--size]; + } + + /** + * Pushes an element onto the stack. + * @param element element + */ + public final void push(final long element) { + add(element); + } + + /** + * Returns an array with all elements. + * @return array + */ + public final long[] toArray() { + return Arrays.copyOf(list, size); + } + + /** + * Returns an array with all elements and invalidates the internal array. + * Warning: the function must only be called if the list is discarded afterwards. + * @return array (internal representation!) + */ + public long[] finish() { + final long[] lst = list; + list = null; + final int s = size; + return s == lst.length ? lst : Arrays.copyOf(lst, s); + } + + /** + * Sorts the data. + * @return self reference + */ + public LongList sort() { + final int s = size; + if(s > 1) Arrays.sort(list, 0, s); + return this; + } + + @Override + public String toString() { + return Arrays.toString(toArray()); + } +} diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/list/StringList.java basex-8.2.3/basex-core/src/main/java/org/basex/util/list/StringList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/list/StringList.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/list/StringList.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,7 +5,7 @@ import org.basex.util.*; /** - * This is a simple container for strings. + * Resizable-array implementation for strings. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/list/TokenList.java basex-8.2.3/basex-core/src/main/java/org/basex/util/list/TokenList.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/list/TokenList.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/list/TokenList.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,7 +8,7 @@ import org.basex.util.hash.*; /** - * This is a simple container for tokens (byte arrays). + * Resizable-array implementation for tokens (byte arrays). * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/options/NumberOption.java basex-8.2.3/basex-core/src/main/java/org/basex/util/options/NumberOption.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/options/NumberOption.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/options/NumberOption.java 2015-07-14 10:54:40.000000000 +0000 @@ -8,7 +8,7 @@ */ public final class NumberOption extends Option { /** Default value. */ - public final Integer value; + private final Integer value; /** * Default constructor. diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/options/Options.java basex-8.2.3/basex-core/src/main/java/org/basex/util/options/Options.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/options/Options.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/options/Options.java 2015-07-14 10:54:40.000000000 +0000 @@ -65,7 +65,7 @@ private final HashMap free = new HashMap<>(); /** Options, cached from an input file. */ - private final StringBuilder user = new StringBuilder(); + private final StringList user = new StringList(); /** Options file. */ private IOFile file; @@ -105,7 +105,7 @@ values.put(e.getKey(), e.getValue()); for(final Entry e : opts.free.entrySet()) free.put(e.getKey(), e.getValue()); - user.append(opts.user); + user.add(opts.user); file = opts.file; } @@ -113,34 +113,34 @@ * Writes the options to disk. */ public final synchronized void write() { - final TokenBuilder tmp = new TokenBuilder(); + final StringList lines = new StringList(); try { - boolean first = true; for(final Option opt : options(getClass())) { final String name = opt.name(); if(opt instanceof Comment) { - if(!first) tmp.add(NL); - tmp.add("# " + name).add(NL); + if(!lines.isEmpty()) lines.add(""); + lines.add("# " + name); } else if(opt instanceof NumbersOption) { final int[] ints = get((NumbersOption) opt); final int is = ints == null ? 0 : ints.length; - for(int i = 0; i < is; ++i) tmp.add(name + i + " = " + ints[i]).add(NL); + for(int i = 0; i < is; ++i) lines.add(name + i + " = " + ints[i]); } else if(opt instanceof StringsOption) { final String[] strings = get((StringsOption) opt); final int ss = strings == null ? 0 : strings.length; - tmp.add(name + " = " + ss).add(NL); - for(int i = 0; i < ss; ++i) tmp.add(name + (i + 1) + " = " + strings[i]).add(NL); + lines.add(name + " = " + ss); + for(int i = 0; i < ss; ++i) lines.add(name + (i + 1) + " = " + strings[i]); } else { - tmp.add(name + " = " + get(opt)).add(NL); + lines.add(name + " = " + get(opt)); } - first = false; } - tmp.add(NL).add(PROPUSER).add(NL); - tmp.add(user.toString()); - final byte[] content = tmp.finish(); + lines.add("").add(PROPUSER).add(user); // only write file if contents have changed - if(!file.exists() || !eq(content, file.read())) file.write(content); + if(update(lines)) { + final TokenBuilder tb = new TokenBuilder(); + for(final String line : lines) tb.add(line).add(NL); + file.write(tb.finish()); + } } catch(final Exception ex) { Util.errln("% could not be written.", file); @@ -416,18 +416,11 @@ * All properties starting with {@code org.basex.} will be assigned as options. */ public final void setSystem() { - // collect parameters that start with "org.basex." - final StringList sl = new StringList(); - for(final Object key : System.getProperties().keySet()) { - final String k = key.toString(); - if(k.startsWith(DBPREFIX)) sl.add(k); - } - // assign properties - for(final String key : sl) { - final String v = System.getProperty(key); + // assign global options + for(final Entry entry : Prop.entries()) { + final String n = entry.getKey(), v = entry.getValue().toString(); try { - final String k = key.substring(DBPREFIX.length()).toUpperCase(Locale.ENGLISH); - if(assign(k, v, -1, false)) Util.debug(k + Text.COLS + v); + if(assign(n, v, -1, false)) Util.debug(n + Text.COLS + v); } catch(final BaseXException ex) { Util.errln(ex); } @@ -502,7 +495,7 @@ @Override public final synchronized String toString() { - // only those options are listed the value of which differs from default value + // only those options are listed whose value differs from default value final StringBuilder sb = new StringBuilder(); for(final Entry e : values.entrySet()) { final String name = e.getKey(); @@ -532,43 +525,6 @@ // STATIC METHODS ===================================================================== /** - * Returns a system property. If necessary, the key will be converted to lower-case - * and prefixed with the {@link Prop#DBPREFIX} string. - * @param option option - * @return value, or empty string - */ - public static String getSystem(final Option option) { - String name = option.name().toLowerCase(Locale.ENGLISH); - if(!name.startsWith(DBPREFIX)) name = DBPREFIX + name; - final String v = System.getProperty(name); - return v == null ? "" : v; - } - - /** - * Sets a system property if it has not been set before. - * @param option option - * @param val value - */ - public static void setSystem(final Option option, final Object val) { - setSystem(option.name(), val); - } - - /** - * Sets a system property if it has not been set before. If necessary, the key will - * be converted to lower-case and prefixed with the {@link Prop#DBPREFIX} string. - * @param key key - * @param val value - */ - public static void setSystem(final String key, final Object val) { - final String name = key.indexOf('.') == -1 ? DBPREFIX + key.toLowerCase(Locale.ENGLISH) : key; - final String value = val.toString(); - if(System.getProperty(name) == null) { - if(value.isEmpty()) System.clearProperty(name); - else System.setProperty(name, val.toString()); - } - } - - /** * Returns a list of allowed keys. * @param option option * @param all allowed values @@ -600,8 +556,6 @@ return opts.toArray(new Option[opts.size()]); } - // PRIVATE METHODS ==================================================================== - /** * Reads the configuration file and initializes the options. * The file is located in the project home directory. @@ -623,7 +577,7 @@ local = true; continue; } - if(local) user.append(line).append(NL); + if(local) user.add(line); if(line.isEmpty() || line.charAt(0) == '#') continue; final int d = line.indexOf('='); @@ -648,7 +602,7 @@ if(local) { // cache local options as system properties - setSystem(name, val); + Prop.put(name, val); } else { try { assign(name, val, num, true); @@ -684,6 +638,25 @@ } /** + * Checks if the options file needs to be updated. + * @param lines lines of new file + * @return result of check + * @throws IOException I/O exception + */ + private boolean update(final StringList lines) throws IOException { + if(!file.exists()) return true; + + int l = 0; + final int ls = lines.size(); + try(final NewlineInput nli = new NewlineInput(file)) { + for(String line; (line = nli.readLine()) != null;) { + if(l == ls || !lines.get(l++).equals(line)) return true; + } + } + return l != ls; + } + + /** * Assigns the specified name and value. * @param name name of option * @param item value of option @@ -692,8 +665,8 @@ * @throws BaseXException database exception * @throws QueryException query exception */ - private synchronized boolean assign(final String name, final Item item, - final boolean error) throws BaseXException, QueryException { + private synchronized boolean assign(final String name, final Item item, final boolean error) + throws BaseXException, QueryException { final Option option = options.get(name); if(option == null) { diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/Prop.java basex-8.2.3/basex-core/src/main/java/org/basex/util/Prop.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/Prop.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/Prop.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,9 +5,12 @@ import java.nio.file.*; import java.security.*; import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.*; import org.basex.core.*; import org.basex.io.*; +import org.basex.util.options.*; /** * This class contains constants and system properties which are used all around the project. @@ -16,8 +19,8 @@ * @author Christian Gruen */ public final class Prop { - /** Private constructor. */ - private Prop() { } + /** Global options. */ + private static final java.util.Map OPTIONS = new ConcurrentHashMap<>(); /** User's home directory. */ public static final String USERHOME; @@ -42,7 +45,7 @@ /** Project name. */ public static final String NAME = "BaseX"; /** Code version (may contain major, minor and optional patch number). */ - public static final String VERSION = version("8.1.1"); + public static final String VERSION = version("8.2.3"); /** Main author. */ public static final String AUTHOR = "Christian Gr\u00FCn"; /** Co-authors (1). */ @@ -108,6 +111,11 @@ /** GUI mode. */ public static boolean gui; + /** Private constructor. */ + private Prop() { } + + // STATIC METHODS ===================================================================== + /** *

    Determines the project's home directory for storing property files * and directories. The directory is chosen as follows:

    @@ -177,4 +185,77 @@ if(revision != null) result.append(' ').append(revision); return result.toString(); } + + /** + * Sets a global option. + * @param option option + * @param value value + */ + public static void put(final Option option, final Object value) { + put(option.name(), value); + } + + /** + * Sets a global option. + * @param name name of the option + * @param value value + */ + public static void put(final String name, final Object value) { + OPTIONS.put(normalizeKey(name), value.toString()); + } + + /** + * Removes a global option. + * @param option option + */ + public static void remove(final Option option) { + OPTIONS.remove(option); + } + + /** + * Returns a system property or global option. System properties override global options. + * @param option option + * @return value, or empty string + */ + public static String get(final Option option) { + String v = System.getProperty(DBPREFIX + option.name().toLowerCase(Locale.ENGLISH)); + if(v == null) v = OPTIONS.get(option); + return v == null ? "" : v; + } + + /** + * Returns the names of all system properties and global options. + * System properties override global options. + * @return entry set + */ + public static Set> entries() { + // override with system properties + final HashMap entries = new HashMap<>(); + entries.putAll(OPTIONS); + // override with system properties + for(final Object key : System.getProperties().keySet()) { + final String name = key.toString(); + if(name.startsWith(DBPREFIX)) entries.put(normalizeKey(name), System.getProperty(name)); + } + return entries.entrySet(); + } + + /** + * Sets a system property if it has not been set before. + * @param key key + * @param value value + */ + public static void setSystem(final String key, final String value) { + if(System.getProperty(key) == null) System.setProperty(key, value); + } + + /** + * Normalizes the key of an option. Removes {@link #DBPREFIX} and converts the key to upper-case. + * @param name name of the option + * @return normalized string + */ + private static String normalizeKey(final String name) { + final String n = name.startsWith(DBPREFIX) ? name.substring(DBPREFIX.length()) : name; + return n.toUpperCase(Locale.ENGLISH); + } } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/Reflect.java basex-8.2.3/basex-core/src/main/java/org/basex/util/Reflect.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/Reflect.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/Reflect.java 2015-07-14 10:54:40.000000000 +0000 @@ -12,7 +12,7 @@ */ public final class Reflect { /** Cached constructors. */ - private static final HashMap> CONS = new HashMap<>(); + private static final HashMap> CONSTRUCTORS = new HashMap<>(); /** Cached classes. */ private static final HashMap> CLASSES = new HashMap<>(); /** Cached fields. */ @@ -122,7 +122,7 @@ final String key = sb.toString(); @SuppressWarnings("unchecked") - Constructor m = (Constructor) CONS.get(key); + Constructor m = (Constructor) CONSTRUCTORS.get(key); if(m == null) { try { try { @@ -131,7 +131,7 @@ m = clazz.getDeclaredConstructor(types); m.setAccessible(true); } - CONS.put(key, m); + CONSTRUCTORS.put(key, m); } catch(final Throwable ex) { Util.debug(ex); } diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/Token.java basex-8.2.3/basex-core/src/main/java/org/basex/util/Token.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/Token.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/Token.java 2015-07-14 10:54:40.000000000 +0000 @@ -36,8 +36,6 @@ public static final byte[] TRUE = token("true"); /** Token 'false'. */ public static final byte[] FALSE = token("false"); - /** Token 'null'. */ - public static final byte[] NULL = token("null"); /** Token 'NaN'. */ public static final byte[] NAN = token("NaN"); /** Token 'INF'. */ diff -Nru basex-8.1.1/basex-core/src/main/java/org/basex/util/XMLAccess.java basex-8.2.3/basex-core/src/main/java/org/basex/util/XMLAccess.java --- basex-8.1.1/basex-core/src/main/java/org/basex/util/XMLAccess.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/java/org/basex/util/XMLAccess.java 2015-07-14 10:54:40.000000000 +0000 @@ -23,9 +23,9 @@ * @param name name * @return iterator */ - public static AxisIter children(final ANode node, final byte[] name) { - final AxisIter children = node.children(); - return new AxisIter() { + public static BasicNodeIter children(final ANode node, final byte[] name) { + final BasicNodeIter children = node.children(); + return new BasicNodeIter() { @Override public ANode next() { for(ANode child; (child = children.next()) != null;) { diff -Nru basex-8.1.1/basex-core/src/main/resources/lang/Dutch.lang basex-8.2.3/basex-core/src/main/resources/lang/Dutch.lang --- basex-8.1.1/basex-core/src/main/resources/lang/Dutch.lang 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/resources/lang/Dutch.lang 2015-07-14 10:54:40.000000000 +0000 @@ -53,7 +53,7 @@ comment = Commentaar community = Gemeenschap compiling = Compileren -connection_error = Communicatie met de server mislukt. +connection_error = Communicatie met de server mislukt copy = Kopieer copy_db = Kopieer database copy_path = Kopieer pad @@ -114,13 +114,6 @@ entries_% = % entries error = Fout evaluating = Evalueren -event_created_% = Event '%' is gecreeerd. -event_dropped_% = Event '%' is verwijderd. -event_exists_% = Event '%' bestaat al. -event_not_watched_% = Event '%' werd niet bekeken. -event_unknown_% = Event '%' is onbekend. -event_watched_% = Event '%' wordt al bekeken. -events_% = % event(s) exec_error_% = Kon % niet uitvoeren exit = Stop expecting_cmd = Commando verwacht. @@ -277,7 +270,7 @@ pkg_replaced_%_% = Package '%' vervangen in %. please_wait = Een ogenblik plot = Plot -port_twice_% = Poort '%' was twee peer gespecificeerd. +port = poort preferences = Voorkeuren printed = Geprint printing = Printen @@ -343,9 +336,9 @@ skipped = Overgeslagen sort = Sorteer split_input_lines = Splits input op in regels -srv_running = Server loopt al of toegang ontzegd. -srv_started_port_% = Server is gestart (poort: %) -srv_stopped_port_% = Server is gestopt (poort: %) +srv_running = Server loopt al of toegang ontzegd +srv_started = Server is gestart +srv_stopped = Server is gestopt standard = Standaard status_bar = Statusbalk stemming = Stam zoeken @@ -380,7 +373,6 @@ unknown_host_x = Onbekende host '%'. unknown_option_% = Onbekende optie '%'. unknown_user_% = Gebruiker '%' is onbekend. -unwatching_event_% = Event '%' niet langer gemonitord. up_to_date = bijgewerkt updated = Bijgewerkt use_catalog_file = Gebruik XML Catalog bestand @@ -398,7 +390,6 @@ version = Versie view = View visualization = Visualisatie -watching_event_% = Monitor event '%'. whole_word = Heel woord write_locking = Write Locking yes = Ja @@ -448,7 +439,6 @@ c_create22 = Maakt een backup van database [%] c_create23 = Maakt de opgegeven index c_create24 = Creeert de opgegeven gebruiker -c_create25 = Creeert het event c_delete1 = Verwijder resources uit de database. c_delete2 = Verwijdert resources uit de actuele database. c_drop1 = Verwijder database, index, gebruiker, backup of event. @@ -457,7 +447,6 @@ c_drop22 = Verwijdert de opgegeven index c_drop23 = Verwijdert de opgegeven gebruiker (van een database). c_drop24 = Verwijdert de database backup -c_drop25 = Verwijdert de event c_execute1 = Voer commando script uit. c_execute2 = Voer het bestand [%] als commando script uit. c_exit1 = Stop applicatie. @@ -514,7 +503,6 @@ c_show23 = Toont actuele database sessies. c_show24 = Toont gebruikers (van een database). c_show25 = Toont backups. -c_show26 = Toont events. c_store1 = Sla ruwe data op. c_store2 = Slaat ruwe data op in de gespecificeerde [%]. c_test1 = Run XQUnit tests. diff -Nru basex-8.1.1/basex-core/src/main/resources/lang/English.lang basex-8.2.3/basex-core/src/main/resources/lang/English.lang --- basex-8.1.1/basex-core/src/main/resources/lang/English.lang 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/resources/lang/English.lang 2015-07-14 10:54:40.000000000 +0000 @@ -53,7 +53,7 @@ comment = Comment community = Community compiling = Compiling -connection_error = Can't communicate with the server. +connection_error = Connection failed copy = Copy copy_db = Copy Database copy_path = Copy Path @@ -114,13 +114,6 @@ entries_% = % entries error = Error evaluating = Evaluating -event_created_% = Event '%' was created. -event_dropped_% = Event '%' was dropped. -event_exists_% = Event '%' already exists. -event_not_watched_% = Event '%' was not watched. -event_unknown_% = Event '%' is unknown. -event_watched_% = Already watching event '%'. -events_% = % event(s) exec_error_% = Could not execute % exit = Exit expecting_cmd = Expecting command. @@ -277,7 +270,7 @@ pkg_replaced_%_% = Package '%' replaced in %. please_wait = Please Wait plot = Plot -port_twice_% = Port '%' was specified twice. +port = port preferences = Preferences printed = Printed printing = Printing @@ -343,9 +336,9 @@ skipped = Skipped sort = Sort split_input_lines = Splits input into lines -srv_running = Server is running or permission was denied. -srv_started_port_% = Server was started (port: %) -srv_stopped_port_% = Server was stopped (port: %) +srv_running = Server is running or permission was denied +srv_started = Server was started +srv_stopped = Server was stopped standard = Standard status_bar = Status Bar stemming = Stemming @@ -380,7 +373,6 @@ unknown_host_x = Unknown host '%'. unknown_option_% = Unknown option '%'. unknown_user_% = Unknown user '%'. -unwatching_event_% = Event '%' unwatched. up_to_date = Up-to-date updated = Updated use_catalog_file = Use XML Catalog file @@ -398,7 +390,6 @@ version = Version view = View visualization = Visualization -watching_event_% = Watch event '%'. whole_word = Whole Word write_locking = Write Locking yes = Yes @@ -448,7 +439,6 @@ c_create22 = creates a backup of the database [%] c_create23 = creates the specified index c_create24 = creates the specified user -c_create25 = creates the event c_delete1 = Delete resources from database. c_delete2 = Deletes resources from the currently opened database. c_drop1 = Drop database, index, user, backup or event. @@ -457,7 +447,6 @@ c_drop22 = drops the specified index c_drop23 = drops the specified user (on a database). c_drop24 = drops the database backup -c_drop25 = drops the event c_execute1 = Execute command script. c_execute2 = Executes the specified [%] as command script. c_exit1 = Exit application. @@ -514,7 +503,6 @@ c_show23 = shows current database sessions. c_show24 = shows users (on a database). c_show25 = shows backups. -c_show26 = shows events. c_store1 = Store raw data. c_store2 = Stores raw data to the specified [%]. c_test1 = Run XQUnit tests. diff -Nru basex-8.1.1/basex-core/src/main/resources/lang/French.lang basex-8.2.3/basex-core/src/main/resources/lang/French.lang --- basex-8.1.1/basex-core/src/main/resources/lang/French.lang 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/resources/lang/French.lang 2015-07-14 10:54:40.000000000 +0000 @@ -53,7 +53,7 @@ comment = Commentaire community = Communauté compiling = Compilation -connection_error = Pas de communication avec le serveur. +connection_error = Pas de communication avec le serveur copy = Copier copy_db = Copier la base de données copy_path = Copier le chemin @@ -114,13 +114,6 @@ entries_% = % entrées error = Erreur evaluating = Évaluation -event_created_% = L'évènement '%' a été créé. -event_dropped_% = L'évènement '%' a été supprimé. -event_exists_% = L'évènement '%' existe déjà. -event_not_watched_% = L'évènement '%' n'est pas observé. -event_unknown_% = L'évènement '%' est inconnu. -event_watched_% = L'évènement '%' est déjà observé. -events_% = % évènement(s) exec_error_% = Impossible d'exécuter % exit = Quitter expecting_cmd = Commande attendue. @@ -277,7 +270,7 @@ pkg_replaced_%_% = Paquet '%' remplacé (%) please_wait = Veuillez patienter plot = Diagramme -port_twice_% = Le port '%' a été spécifié deux fois. +port = port preferences = Préférences printed = Imprimé printing = Impression @@ -343,9 +336,9 @@ skipped = Ignoré sort = Sort split_input_lines = Éclater l'entrée en lignes -srv_running = Serveur déjà démarré ou permission refusée. -srv_started_port_% = Serveur démarré (port: %) -srv_stopped_port_% = Serveur arrêté (port: %) +srv_running = Serveur déjà démarré ou permission refusée +srv_started = Serveur démarré +srv_stopped = Serveur arrêté standard = Standard status_bar = Barre d'état stemming = Stemming @@ -380,7 +373,6 @@ unknown_host_x = Serveur '%' inconnu. unknown_option_% = Option '%' inconnue. unknown_user_% = Utilisateur '%' inconnu. -unwatching_event_% = Evènement '%' non observé. up_to_date = À jour updated = Mis à jour use_catalog_file = Utiliser un fichier de catalogue XML Catalog @@ -398,7 +390,6 @@ version = Version view = Affichage visualization = Visualisation -watching_event_% = Observation de l'évènement '%'. whole_word = Mot entier write_locking = Blocage en écriture yes = Oui @@ -448,7 +439,6 @@ c_create22 = crée une sauvegarde pour la base de données [%] c_create23 = crée l'index specifié c_create24 = crée l'utilisateur specifié -c_create25 = crée l'évènement c_delete1 = Supprimer les ressources. c_delete2 = Supprime les ressources de la base de données courante. c_drop1 = Supprimer la base de données, l'index, l'utilisateur, la sauvegarde ou l'évènement. @@ -457,7 +447,6 @@ c_drop22 = supprime l'index spécifié c_drop23 = supprime l'utilisateur spécifié (sur une base de données). c_drop24 = supprime la sauvegarde de la base de données -c_drop25 = supprime l'évènement c_execute1 = Exécuter le script c_execute2 = Exécute le [%] spécifié comme script c_exit1 = Quitter l'application. @@ -514,7 +503,6 @@ c_show23 = montre les sessions de la base de données courante. c_show24 = montre les utilisateurs (sur une base de données). c_show25 = montre les backups. -c_show26 = montre les événements. c_store1 = Enregistrer les données brutes. c_store2 = Enregistre les données brutes dans le [%] specifié. c_test1 = Exécute les tests XQUnit. diff -Nru basex-8.1.1/basex-core/src/main/resources/lang/German.lang basex-8.2.3/basex-core/src/main/resources/lang/German.lang --- basex-8.1.1/basex-core/src/main/resources/lang/German.lang 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/resources/lang/German.lang 2015-07-14 10:54:40.000000000 +0000 @@ -53,7 +53,7 @@ comment = Kommentar community = Community compiling = Kompilierung -connection_error = Der Server ist nicht erreichbar. +connection_error = Der Server ist nicht erreichbar copy = Kopieren copy_db = Datebank kopieren copy_path = Pfad kopieren @@ -114,13 +114,6 @@ entries_% = % Einträge error = Fehler evaluating = Ausführung -event_created_% = Event '%' erzeugt. -event_dropped_% = Event '%' entfernt. -event_exists_% = Event '%' existiert schon. -event_not_watched_% = Event '%' wurde nicht beobachtet. -event_unknown_% = Unbekannter Event '%'. -event_watched_% = Event '%' wird schon beobachtet. -events_% = % Event(s) exec_error_% = % konnte nicht ausgeführt werden exit = Beenden expecting_cmd = Befehl erwartet. @@ -277,7 +270,7 @@ pkg_replaced_%_% = Das Paket '%' wurde ersetzt (%). please_wait = Bitte warten plot = Plot -port_twice_% = Der Port '%' wurde mehrfach angegeben. +port = Port preferences = Einstellungen printed = Ausgegeben printing = Ausgabe @@ -343,9 +336,9 @@ skipped = Übersprungen sort = Sortieren split_input_lines = Trennt den Text in einzelne Zeilen auf -srv_running = Der Server läuft, oder der Zugriff wurde untersagt. -srv_started_port_% = Server wurde gestartet (Port: %) -srv_stopped_port_% = Server wurde gestoppt (Port: %) +srv_running = Der Server läuft, oder der Zugriff wurde untersagt +srv_started = Server wurde gestartet +srv_stopped = Server wurde gestoppt standard = Standard status_bar = Statusleiste stemming = Stemming @@ -380,7 +373,6 @@ unknown_host_x = Unbekannter Host '%'. unknown_option_% = Unbekannte Option '%'. unknown_user_% = Unbekannter Benutzer '%'. -unwatching_event_% = Von Event '%' abgemeldet. up_to_date = Up-to-date updated = Updates use_catalog_file = XML Katalog-Datei @@ -398,7 +390,6 @@ version = Version view = Ansicht visualization = Visualisierung -watching_event_% = Beobachte Event '%'. whole_word = Ganzes Wort write_locking = Write Locks yes = Ja @@ -448,7 +439,6 @@ c_create22 = erstellt ein Backup der Datenbank [%] c_create23 = erstellt den angegebenen Index c_create24 = erstellt den angegebenen Benutzer -c_create25 = erstellt den angegebenen Event c_delete1 = Entfernen von Ressourcen. c_delete2 = Entfernt Ressourcen aus der geöffneten Datenbank. c_drop1 = Entfernen einer Datenbank, eines Index oder Benutzers. @@ -457,7 +447,6 @@ c_drop22 = entfernt den angegebenen Index c_drop23 = entfernt den angegebenen Benutzer (einer Datenbank). c_drop24 = entfernt das Datenbank-Backup -c_drop25 = entfernt den Event c_execute1 = Execute command script. c_execute2 = Executes the specified [%] as command script. c_exit1 = Beenden des Programms. @@ -514,7 +503,6 @@ c_show23 = zeigt aktuelle Datenbankverbindungen. c_show24 = zeigt registrierte Benutzer (einer Datenbank). c_show25 = zeigt Backups an. -c_show26 = zeigt Events an. c_store1 = Speicherung von Rohdaten. c_store2 = Speichert Rohdaten am angegebenen Pfad [%]. c_test1 = Ausführung von XQUnit-Tests. diff -Nru basex-8.1.1/basex-core/src/main/resources/lang/Hungarian.lang basex-8.2.3/basex-core/src/main/resources/lang/Hungarian.lang --- basex-8.1.1/basex-core/src/main/resources/lang/Hungarian.lang 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/resources/lang/Hungarian.lang 2015-07-14 10:54:40.000000000 +0000 @@ -53,7 +53,7 @@ comment = Megjegyzés community = Közösség compiling = Fordítás -connection_error = Nincs kapcsolat a szerverrel. +connection_error = Nincs kapcsolat a szerverrel copy = Másolás copy_db = Adatbázis másolása copy_path = Útvonal másolása @@ -114,13 +114,6 @@ entries_% = % bejegyzés error = Hiba evaluating = Végrehajtás -event_created_% = '%' esemény létrejött. -event_dropped_% = '%' eseményt eldobta. -event_exists_% = '%' esemény már létezik. -event_not_watched_% = '%' eseményt nem figyelt. -event_unknown_% = '%' esemény ismeretlen. -event_watched_% = '%' eseményt már figyeli. -events_% = % esemény exec_error_% = % nem futtatható exit = Kilépés expecting_cmd = Parancs az elvárt. @@ -277,7 +270,7 @@ pkg_replaced_%_% = '%' csomag lecserélve % alatt. please_wait = Kérem várjon plot = Pontok -port_twice_% = '%' port kétszer lett megadva. +port = port preferences = Beállítások printed = Megjelenítve printing = Megjelenítés @@ -301,7 +294,7 @@ reopen_file_% = Újra megnyitja (%) és eldobja a változásokat? replace_all = Összes cseréje replace_with = Csere erre: -repository_path = Repository Path +repository_path = Adattár úvonala requires_restart = újraindítás szükséges res_added_% = Erőforrás(ok) hozzáadva % alatt. res_deleted_%_% = % erőforrás törölve % alatt. @@ -343,9 +336,9 @@ skipped = Kihagyva sort = Rendezés split_input_lines = Bemenet sorokra tördelése -srv_running = Szerver fut vagy hozzáférés megtagadva. -srv_started_port_% = Szerver elindult (port: %) -srv_stopped_port_% = Szerver leállt (port: %) +srv_running = Szerver fut vagy hozzáférés megtagadva +srv_started = Szerver elindult +srv_stopped = Szerver leállt standard = Standard status_bar = Állapotsor stemming = Csupaszítás @@ -380,7 +373,6 @@ unknown_host_x = Ismeretlen kiszolgáló '%'. unknown_option_% = Ismeretlen beállítás '%'. unknown_user_% = Ismeretlen felhasználó '%'. -unwatching_event_% = '%' esemény nem figyelt. up_to_date = Naprakész updated = Frissítve use_catalog_file = XML katalógusfájl használata @@ -398,7 +390,6 @@ version = Verzió view = Nézet visualization = Megjelenítés -watching_event_% = '%' esemény figyelése. whole_word = Egész szó write_locking = Írási zárolása yes = Igen @@ -448,7 +439,6 @@ c_create22 = létrehozza az adatbázis egy biztonsági másolatát [%] c_create23 = létrehozza a kívánt indexet c_create24 = létrehozza a kívánt felhasználót -c_create25 = létrehozza az eseményt c_delete1 = Erőforrások törlése az adatbázisból. c_delete2 = Törli az erőforrásokat a jelenleg megnyitott adatbázisból. c_drop1 = Adatbázis, index, felhasználó, biztonsági másolat vagy esemény eldobása. @@ -457,7 +447,6 @@ c_drop22 = eldobja a kívánt indexet c_drop23 = eldobja a kívánt felhasználót (az adatbázisban). c_drop24 = eldobja az adatbázis biztonsági másolatot -c_drop25 = eldobja az eseményt c_execute1 = Parancsszkript futtatása. c_execute2 = Futtatja parancsszkriptként a kívánt [%]-t. c_exit1 = Alkalmazás bezárása. @@ -514,7 +503,6 @@ c_show23 = megjelenít jelenlegi adatbázis munkameneteket. c_show24 = megjelenít felhasználókat (az adatbázisban). c_show25 = megjelenít biztonsági mentéseket. -c_show26 = megjelenít eseményeket. c_store1 = Raw adat tárolása. c_store2 = Raw adatot tárol a kívánt [%]-n. c_test1 = XQUnit tesztek futtatása. diff -Nru basex-8.1.1/basex-core/src/main/resources/lang/Indonesian.lang basex-8.2.3/basex-core/src/main/resources/lang/Indonesian.lang --- basex-8.1.1/basex-core/src/main/resources/lang/Indonesian.lang 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/resources/lang/Indonesian.lang 2015-07-14 10:54:40.000000000 +0000 @@ -53,7 +53,7 @@ comment = Komentar community = Komunitas compiling = Mengompilasi -connection_error = Tidak dapat terhubung dengan server. +connection_error = Tidak dapat terhubung dengan server copy = Salin copy_db = Salin Basisdata copy_path = Salin Rintis @@ -114,13 +114,6 @@ entries_% = % masukan error = Galat evaluating = Mengevaluasi -event_created_% = Kejadian '%' telah dibuat. -event_dropped_% = Kejadian '%' telah dihapus. -event_exists_% = Kejadian '%' sudah ada. -event_not_watched_% = Kejadian '%' tidak diamati. -event_unknown_% = Kejadian '%' tidak dikenal. -event_watched_% = Sudah sedang mengamati kejadian '%'. -events_% = % kejadian exec_error_% = Tidak dapat menjalankan % exit = Keluar expecting_cmd = Mengharapkan perintah. @@ -277,7 +270,7 @@ pkg_replaced_%_% = Paket '%' diganti dalam %. please_wait = Mohon tunggu plot = Plot -port_twice_% = Port '%' dinyatakan dua kali. +port = port preferences = Perasa printed = Tercetak printing = Mencetak @@ -343,9 +336,9 @@ skipped = Terlewat sort = Sortir split_input_lines = Potong masukan menjadi baris -srv_running = Servis sudah berjalan atau izin ditolak. -srv_started_port_% = Server sudah dijalankan (port: %) -srv_stopped_port_% = Server sudah dihentikan (port: %) +srv_running = Servis sudah berjalan atau izin ditolak +srv_started = Server sudah dijalankan +srv_stopped = Server sudah dihentikan standard = Standar status_bar = Batang status stemming = Memotong @@ -380,7 +373,6 @@ unknown_host_x = Host '%' tidak diketahui. unknown_option_% = Opsi '%' tidak diketahui. unknown_user_% = Pengguna '%' tidak diketahui. -unwatching_event_% = Kejadian '%' tidak diamati. up_to_date = Mutakhir updated = Termutakhirkan use_catalog_file = Gunakan berkas katalog XML @@ -398,7 +390,6 @@ version = Versi view = Lihat visualization = Visualisasi -watching_event_% = Amati kejadian '%'. whole_word = Seluruh kata write_locking = Tulis pengunci yes = Ya @@ -448,7 +439,6 @@ c_create22 = buat cadangan dari basisdata [%] c_create23 = buat indeks yang ditentukan c_create24 = buat pengguna yang ditentukan -c_create25 = buat kejadian c_delete1 = Hapus sumber daya dari basisdata. c_delete2 = Hapus sumber daya dari basisdata yang sedang terbuka. c_drop1 = Hapus basisdata, indeks, cadangan, atau pengguna. @@ -457,7 +447,6 @@ c_drop22 = hapus indeks yang ditentukan c_drop23 = hapus pengguna yang ditentukan (dalam satu basisdata). c_drop24 = hapus cadangan basisdata -c_drop25 = hapus kejadian c_execute1 = Jalankan skrip perintah. c_execute2 = Jalankan [%] sebagai skrip perintah. c_exit1 = Keluar dari aplikasi. @@ -514,7 +503,6 @@ c_show23 = tampilkan sesi basisdata kini. c_show24 = tampilkan pengguna (pada satu basisdata). c_show25 = tampilkan cadangan. -c_show26 = tampilkan kejadian. c_store1 = Simpan data mentah. c_store2 = Simpan data mentah ke [%] yang ditetapkan. c_test1 = Jalankan pengujian XQUnit. diff -Nru basex-8.1.1/basex-core/src/main/resources/lang/Italian.lang basex-8.2.3/basex-core/src/main/resources/lang/Italian.lang --- basex-8.1.1/basex-core/src/main/resources/lang/Italian.lang 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/resources/lang/Italian.lang 2015-07-14 10:54:40.000000000 +0000 @@ -53,7 +53,7 @@ comment = Commento community = Comunità compiling = Compilando -connection_error = Impossibile comunicare con il Server. +connection_error = Impossibile comunicare con il Server copy = Copia copy_db = Copia base di dati copy_path = Copia percorso @@ -114,13 +114,6 @@ entries_% = % occorrenze error = Errore evaluating = Eseguendo -event_created_% = L'evento '%' è stato creato. -event_dropped_% = L'evento '%' è stato rimosso. -event_exists_% = L'evento '%' esiste già. -event_not_watched_% = L'evento '%' non è stato osservato. -event_unknown_% = L'evento '%' è sconosciuto. -event_watched_% = Stai già osservando l'evento '%'. -events_% = % event(i) exec_error_% = Impossibile eseguire % exit = Esci expecting_cmd = Attendo un comando. @@ -277,7 +270,7 @@ pkg_replaced_%_% = Pacchetto '%' sostituiti in %. please_wait = Attendi plot = Grafico -port_twice_% = La porta '%' è stata specificata due volte. +port = porta preferences = Impostazioni printed = Stampato printing = Stampando @@ -343,9 +336,9 @@ skipped = Saltato sort = Ordina split_input_lines = Dividi l'input in linee -srv_running = Il server sta lavorando o il permesso è stato negato. -srv_started_port_% = Server è stato avviato (porta: %) -srv_stopped_port_% = Server è stato spento (porta: %) +srv_running = Il server sta lavorando o il permesso è stato negato +srv_started = Server è stato avviato +srv_stopped = Server è stato spento standard = Normale status_bar = Barra di stato stemming = Radice linguistica (Inglese) @@ -380,7 +373,6 @@ unknown_host_x = Host sconosciuto '%'. unknown_option_% = Opzione sconosciuta '%'. unknown_user_% = L'utente '%' è sconosciuto. -unwatching_event_% = Evento '%' non osservato. up_to_date = Aggiornato updated = Aggiornato use_catalog_file = Usa il documento del catalogo XML @@ -398,7 +390,6 @@ version = Versione view = Visualizza visualization = Visualizzazione -watching_event_% = Guarda l'evento '%'. whole_word = Parola Intera write_locking = Locking in scrittura yes = Sì @@ -448,7 +439,6 @@ c_create22 = crea un backup della base di dati [%] c_create23 = crea l'indice specificato c_create24 = crea l'utente specificato -c_create25 = crea un evento c_delete1 = Rimuovi risorse. c_delete2 = Rimuovi risorse della base di dati corrente. c_drop1 = Cancella base di dati, indice, utente, backup o evento. @@ -457,7 +447,6 @@ c_drop22 = cancella l'indice specificato c_drop23 = cancella l'utente specificato (su una base di dati). c_drop24 = rimuovi il backup della base di dati -c_drop25 = rimuovi l'evento c_execute1 = Esegui lo script dei comandi. c_execute2 = Esegui [%] come script di comandi. c_exit1 = Esci dall'applicazione. @@ -514,7 +503,6 @@ c_show23 = mostra le sessioni aperte. c_show24 = mostra gli utenti (su una base di dati). c_show25 = mostra i backup. -c_show26 = mostra gli event. c_store1 = Archivia i dati grezzi. c_store2 = Archivia i dati grezzi in [%]. c_test1 = Lancia i test XQUnit. diff -Nru basex-8.1.1/basex-core/src/main/resources/lang/Japanese.lang basex-8.2.3/basex-core/src/main/resources/lang/Japanese.lang --- basex-8.1.1/basex-core/src/main/resources/lang/Japanese.lang 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/resources/lang/Japanese.lang 2015-07-14 10:54:40.000000000 +0000 @@ -53,7 +53,7 @@ comment = コメント community = コミュニティ compiling = コンパイル中 -connection_error = サーバーと通信できません。 +connection_error = サーバーと通信できません copy = コピー copy_db = ドキュメントのコピー copy_path = パスのコピー @@ -114,13 +114,6 @@ entries_% = % 個のエントリー error = エラー evaluating = 計算中 -event_created_% = イベント '%' が作成されました。 -event_dropped_% = イベント '%' が削除されました。 -event_exists_% = イベント '%' は既に存在しています。 -event_not_watched_% = イベント '%' は監視されていません。 -event_unknown_% = イベント '%' は不明です。 -event_watched_% = 既にイベントを監視しています '%'。 -events_% = % イベント exec_error_% = %を実行できませんでした。 exit = 終了 expecting_cmd = 予想されるコマンド @@ -277,7 +270,7 @@ pkg_replaced_%_% = パッケージ '%' は % に置き換えられました。 please_wait = お待ち下さい plot = プロット -port_twice_% = ポート番号 '%' に対する指定が重複しています。 +port = port preferences = 設定 printed = 印刷しました printing = 印刷中 @@ -343,9 +336,9 @@ skipped = スキップしました。 sort = ソート split_input_lines = 改行で分割する -srv_running = サーバーが既に起動しているか、権限が足りません。 -srv_started_port_% = サーバーを開始しました (port: %) -srv_stopped_port_% = サーバーを停止しました (port: %) +srv_running = サーバーが既に起動しているか、権限が足りません +srv_started = サーバーを開始しました +srv_stopped = サーバーを停止しました standard = 通常 status_bar = ステータスバー stemming = 語幹処理 @@ -380,7 +373,6 @@ unknown_host_x = '%' は不明なホスト名です。 unknown_option_% = '%' は不明なオプションです。 unknown_user_% = ユーザー '%' が見付かりません。 -unwatching_event_% = イベント '%' は監視外です。 up_to_date = 最新 updated = 更新しました use_catalog_file = XMLカタログファイルを使用する @@ -398,7 +390,6 @@ version = バージョン view = ビュー visualization = ビジュアライゼーション -watching_event_% = イベント '%' を監視しています. whole_word = 単語全体 write_locking = 書き込みロック yes = はい @@ -448,7 +439,6 @@ c_create22 = データベースのバックアップを [%] に作成します c_create23 = 指定されたインデックスを作成します。 c_create24 = 指定されたユーザーを作成します。 -c_create25 = イベントを作成します。 c_delete1 = リソースの削除 c_delete2 = 現在のデータベースからリソーストを削除します。 c_drop1 = データベース、インデックス、ユーザ、バックアップ、またはイベントの削除 @@ -457,7 +447,6 @@ c_drop22 = 指定されたインデックスを削除します。 c_drop23 = 指定された (database 上の) ユーザを削除します。 c_drop24 = データベースのバックアップを削除します。 -c_drop25 = イベントを削除します。 c_execute1 = コマンドスクリプトを実行します。 c_execute2 = 指定された [%] をコマンドスクリプトとして実行します。 c_exit1 = アプリケーションの終了 @@ -514,7 +503,6 @@ c_show23 = 現在のデータベースセッションを表示します。 c_show24 = データベースにアクセスできるユーザーを表示します。 c_show25 = バックアップを表示します。 -c_show26 = イベントを表示します。 c_store1 = Rawデータを格納します。 c_store2 = Rawデータを指定された [%] に格納します。 c_test1 = XQUnit テストを実行します。 diff -Nru basex-8.1.1/basex-core/src/main/resources/lang/Mongolian.lang basex-8.2.3/basex-core/src/main/resources/lang/Mongolian.lang --- basex-8.1.1/basex-core/src/main/resources/lang/Mongolian.lang 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/resources/lang/Mongolian.lang 2015-07-14 10:54:40.000000000 +0000 @@ -53,7 +53,7 @@ comment = Сэтгэгдэл community = Олон нийтийн харилцаа compiling = Хөрвүүлэлт -connection_error = Сервертэй холбогдож чадахгүй байна. +connection_error = Сервертэй холбогдож чадахгүй байна copy = Хуулах copy_db = Өгөгдлийн санг хуулах copy_path = Байршил хуулах @@ -114,13 +114,6 @@ entries_% = % оролтууд error = Алдаатай evaluating = Тооцоололт -event_created_% = Үйл ажиллагаа(event) '%' үүссэн. -event_dropped_% = Үйл ажиллагаа(event) '%' устгагдсан. -event_exists_% = Үйл ажиллагаа(event) '%' өмнө үүссэн байна. -event_not_watched_% = Үйл ажиллагаа(event) '%' хянаагүй байна. -event_unknown_% = Үйл ажиллагаа(event) '%' тодорхойгүй. -event_watched_% = Үйл ажиллагаа(event) '%' хянагдсан байна. -events_% = % Үйл ажиллагаа(event(s))-үүд exec_error_% = Хөрвүүлэгдэж чадахгүй байна % exit = Хаах expecting_cmd = Коммандаас гадуур байна. @@ -277,7 +270,7 @@ pkg_replaced_%_% = '%' багц %-нд солигдсон. please_wait = Түр хүлээнэ үү plot = Схем -port_twice_% = Порт '%' нь дахин тодорхойлогдсон байна. +port = Порт preferences = Тохиргоонууд printed = Хэвлэгдсэн printing = Хэвлэлт @@ -343,9 +336,9 @@ skipped = Алгассан sort = Sort split_input_lines = Мөрүүдэд хуваах -srv_running = Серверийн ажиллагаа эсвэл эрх олголт амжилтгүй. -srv_started_port_% = Сервер ажиллаж байна (Порт: %) -srv_stopped_port_% = Сервер зогссон (Порт: %) +srv_running = Серверийн ажиллагаа эсвэл эрх олголт амжилтгүй +srv_started = Сервер ажиллаж байна +srv_stopped = Сервер зогссон standard = Стандарт status_bar = Статус бар stemming = Зогсоолт @@ -380,7 +373,6 @@ unknown_host_x = Тодорхойлогдоогүй '%' хост байна. unknown_option_% = '%' тодорхойгүй тохиргоо. unknown_user_% = Хэрэглэгч '%' тодорхойгүй. -unwatching_event_% = Үйл ажиллагаа(event) '%' хянагдаагүй. up_to_date = Саяхан шинэчлэгдсэн, шинэ updated = Шинэчлэгдсэн use_catalog_file = XML каталог файл ашиглах @@ -398,7 +390,6 @@ version = Хувилбар view = Харагдах байдал visualization = Visualization -watching_event_% = Үйл ажиллагаа(event) '%' хянах. whole_word = Whole Word write_locking = Write Locking yes = Тийм @@ -448,7 +439,6 @@ c_create22 = [%] Нөөцлөх өгөгдлийн санг үүсгэх c_create23 = Тодорхойлогдсон индекс үүсгэх c_create24 = Тодорхойлогдсон хэрэглэгч үүсгэх -c_create25 = үйл ажиллагаа (event) үүсгэх c_delete1 = Өгөгдлийн сангаас документ устгах. c_delete2 = Нээллтэй байгаа өгөгдлийн сангаас документ устгах. c_drop1 = Өгөгдлийн сан, хэрэглэгч эсвэл индекс устгах. @@ -457,7 +447,6 @@ c_drop22 = Тодорхойлогдсон индексийг устгах c_drop23 = Тодорхойлогдсон хэрэглэгчийг устгах (өгөгдлийн сан дах). c_drop24 = Өгөгдлийн сангийн нөөцлөлтийг устгах -c_drop25 = Үйл ажиллагаануудыг устгах c_execute1 = Execute command script. c_execute2 = Executes the specified [%] as command script. c_exit1 = Програмыг хаах. @@ -514,7 +503,6 @@ c_show23 = Нээлттэй байгаа өгөгдлийн сангийн суулт(session)-г харуулах. c_show24 = Хэрэглэгчдийг харуулах (өгөгдлийн сан дээр). c_show25 = Нөөцлөлтийг харуулах. -c_show26 = Үйл ажиллагааг харуулах. c_store1 = Боловсруулалт хийгдээгүй мэдээллийг хадгалах. c_store2 = [%]-нд боловсруулалт хийгдээгүй мэдээллийг хадгалах. c_test1 = Run XQUnit tests. diff -Nru basex-8.1.1/basex-core/src/main/resources/lang/Romanian.lang basex-8.2.3/basex-core/src/main/resources/lang/Romanian.lang --- basex-8.1.1/basex-core/src/main/resources/lang/Romanian.lang 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/resources/lang/Romanian.lang 2015-07-14 10:54:40.000000000 +0000 @@ -53,7 +53,7 @@ comment = Observație community = Comunitate compiling = Compilator -connection_error = Nu se poate comunica cu serverul. +connection_error = Nu se poate comunica cu serverul copy = Copiaza copy_db = Copiaza baza de date copy_path = Calea de copiere @@ -114,13 +114,6 @@ entries_% = % intrări error = Eroare evaluating = Evaluarea -event_created_% = Evenimentul '%' a fost creat. -event_dropped_% = Evenimentul '%' a fost sters. -event_exists_% = Evenimentul '%' deja există. -event_not_watched_% = Evenimentul '%' nu a fost vizualizat. -event_unknown_% = Evenimentul '%' este necunoscut. -event_watched_% = Deja in curs de vizualizare a evnimentului '%'. -events_% = % evenimente exec_error_% = Nu s-a putut executa % exit = Ieşire expecting_cmd = Astept comanda. @@ -277,7 +270,7 @@ pkg_replaced_%_% = Pachetul '%' înlocuit în %. please_wait = Vă rog să așteptați plot = Plot -port_twice_% = Portul '%' a fost specificat de două ori. +port = portul preferences = Preferințe printed = Tipărit printing = In curs de tipărire @@ -343,9 +336,9 @@ skipped = Trecute peste sort = Sort split_input_lines = Desparte intrarea în linii -srv_running = Serverul este pornit sau permisiunea a fost refuzata. -srv_started_port_% = Serverul este pornit (portul: %) -srv_stopped_port_% = Serverul a fost oprit (portul: %) +srv_running = Serverul este pornit sau permisiunea a fost refuzata +srv_started = Serverul este pornit +srv_stopped = Serverul a fost oprit standard = Standard status_bar = Bara de stare stemming = Stemming @@ -380,7 +373,6 @@ unknown_host_x = Host necunoscut '%'. unknown_option_% = Opţiune necunoscută '%'. unknown_user_% = Utilizator necunoscut '%'. -unwatching_event_% = Eveniment '%' nesupravegheat. up_to_date = La zi updated = Actualizat use_catalog_file = Utilizaţi fişier XML Catalog @@ -398,7 +390,6 @@ version = Versiune view = Vizualizare visualization = Visualization -watching_event_% = Uita-te la evenimentul '%'. whole_word = Cuvantul intreg write_locking = Write Locking yes = Da @@ -448,7 +439,6 @@ c_create22 = Creează o copie de rezervă a bazei de date [%] c_create23 = Creează indicele specificat c_create24 = Creează utilizatorul specificat -c_create25 = Creeaza eveniment c_delete1 = Ştergeţi resurse din baza de date. c_delete2 = Şterge resursele din baza de date în prezent deschise. c_drop1 = Sterge baze de date, index, utilizator, copie de rezervă sau eveniment. @@ -457,7 +447,6 @@ c_drop22 = Sterge indicele specificat c_drop23 = Sterge utilizator specificat (pe o bază de date). c_drop24 = Sterge baza de date de rezerva -c_drop25 = Sterge eveniment c_execute1 = Executa command script. c_execute2 = Executa [%] specificat ca un command script. c_exit1 = Ieşiţi din aplicaţie. @@ -514,7 +503,6 @@ c_show23 = prezinta sesiuni de baze de date actuale. c_show24 = Afiseaza utilizatorii (unei baze de date). c_show25 = Arată backupurile. -c_show26 = Arată evenimente. c_store1 = Stochează date brute. c_store2 = Stochează date brute la [%]. c_test1 = Run XQUnit tests. diff -Nru basex-8.1.1/basex-core/src/main/resources/lang/Russian.lang basex-8.2.3/basex-core/src/main/resources/lang/Russian.lang --- basex-8.1.1/basex-core/src/main/resources/lang/Russian.lang 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/resources/lang/Russian.lang 2015-07-14 10:54:40.000000000 +0000 @@ -114,13 +114,6 @@ entries_% = Элементов: % error = Ошибка evaluating = Обработка -event_created_% = Событие '%' было создано -event_dropped_% = Событие '%' было удалено -event_exists_% = Событие '%' уже существует -event_not_watched_% = Событие '%' уже не слушается -event_unknown_% = Неизвестное событие '%' -event_watched_% = Уже слушается событие '%' -events_% = Событий: % exec_error_% = Не удалось выполнить % exit = Выход expecting_cmd = Ожидается команда @@ -277,7 +270,7 @@ pkg_replaced_%_% = Пакет '%' был заменен за %. please_wait = Ожидайте plot = График -port_twice_% = Порт '%' был указан дважды +port = порт preferences = Настройки printed = Выведено printing = Вывод на экран @@ -344,8 +337,8 @@ sort = Сортировка split_input_lines = Разделять содержимое на строки srv_running = Сервер уже запущен или недостаточно прав для выполнения операции -srv_started_port_% = Сервер запущен (порт: %) -srv_stopped_port_% = Сервер остановлен (порт: %) +srv_started = Сервер запущен +srv_stopped = Сервер остановлен standard = Обычный status_bar = Строка статуса stemming = Морфологический поиск @@ -380,7 +373,6 @@ unknown_host_x = Неизвестный хост '%' unknown_option_% = Неизвестный параметр '%'. unknown_user_% = Неизвестный пользователь '%'. -unwatching_event_% = Событие '%' перестало слушаться up_to_date = Актуальное состояние updated = Обновлено use_catalog_file = Использовать файл XML каталога @@ -398,7 +390,6 @@ version = Версия view = Вид visualization = Визуализация -watching_event_% = Слушается событие '%' whole_word = Слово целиком write_locking = Блокировка на запись yes = Да @@ -448,7 +439,6 @@ c_create22 = создаст резервную копию базы данных [%] c_create23 = создаст индекс указанного типа c_create24 = создаст указанного пользователя -c_create25 = создаст событие c_delete1 = Удаление ресурсов из базы данных c_delete2 = Удаляет ресурсы в открытой на данный момент базе c_drop1 = Удаление резервной копии, базы данных, события, индекса или пользователя @@ -457,7 +447,6 @@ c_drop22 = удалит указанный индекс c_drop23 = удалит указанного пользователя (в указанной базе данных) c_drop24 = удалит резервную копию базы данных -c_drop25 = удалит событие c_execute1 = Выполнение сценария команд c_execute2 = Исполняет указанный [%] как сценарий команд c_exit1 = Выход из приложения @@ -514,7 +503,6 @@ c_show23 = активные сессии текущей базы данных c_show24 = список пользователей (указанной базы данных) c_show25 = список резервных копий -c_show26 = список событий c_store1 = Сохранить исходный файл c_store2 = Сохраняет исходный файл по указанному пути [%] c_test1 = Выполнить XQUnit тесты diff -Nru basex-8.1.1/basex-core/src/main/resources/lang/Spanish.lang basex-8.2.3/basex-core/src/main/resources/lang/Spanish.lang --- basex-8.1.1/basex-core/src/main/resources/lang/Spanish.lang 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/main/resources/lang/Spanish.lang 2015-07-14 10:54:40.000000000 +0000 @@ -53,7 +53,7 @@ comment = Comentario community = Comunidad compiling = Compilando -connection_error = No se puede comunicar con el servidor. +connection_error = No se puede comunicar con el servidor copy = Copiar copy_db = Copiar Base de Datos copy_path = Copiar Ruta @@ -114,13 +114,6 @@ entries_% = % entradas error = Error evaluating = Evaluando -event_created_% = El evento '%' ha sido creado. -event_dropped_% = El evento '%' ha sido borrado. -event_exists_% = El evento '%' ya existe. -event_not_watched_% = No se ha monitorizado el evento '%'. -event_unknown_% = El evento '%' es desconocido. -event_watched_% = Ya se está monitorizando el evento '%'. -events_% = % evento(s) exec_error_% = No se pudo ejecutar % exit = Salir expecting_cmd = Esperando comando. @@ -277,7 +270,7 @@ pkg_replaced_%_% = Paquete '%' reemplazado en %. please_wait = Por favor, espere plot = Diagrama -port_twice_% = El puerto '%' se ha especificado dos veces. +port = puerto preferences = Preferencias printed = Impreso printing = Imprimiendo @@ -344,8 +337,8 @@ sort = Ordenar split_input_lines = Separar salida en varias líneas srv_running = El servidor está ejecutándose o el permiso fue denegado -srv_started_port_% = El servidor ha arrancado (puerto: %) -srv_stopped_port_% = El servidor ha parado (puerto: %) +srv_started = El servidor ha arrancado +srv_stopped = El servidor ha parado standard = Estándar status_bar = Barra de estado stemming = Reducción a la raíz @@ -380,7 +373,6 @@ unknown_host_x = Host desconocido '%'. unknown_option_% = Opción desconocida '%'. unknown_user_% = Usuario desconocido '%'. -unwatching_event_% = Evento '%' no monitorizado. up_to_date = Actualizado updated = Actualizado use_catalog_file = Utilizar el fichero XML de Catálogo @@ -398,7 +390,6 @@ version = Versión view = Vista visualization = Visualización -watching_event_% = Monitorizar evento '%'. whole_word = Palabra Entera write_locking = Bloqueo de Escritura yes = Sí @@ -448,7 +439,6 @@ c_create22 = crea la copia de seguridad de la Base de Datos [%] c_create23 = crea el índice especificado c_create24 = crea el usuario especificado -c_create25 = crea el evento c_delete1 = Borrar recursos de la Base de Datos. c_delete2 = Borra recursos de la Base de Datos abierta. c_drop1 = Borrar una Base de Datos, índice, usuario, copia de seguridad o evento. @@ -457,7 +447,6 @@ c_drop22 = Borra el índice especificado c_drop23 = Borra el usuario especificado (de una Base de Datos). c_drop24 = Borra la copia de seguridad de la Base de Datos -c_drop25 = borra el evento c_execute1 = Ejecuta un conjunto de comandos. c_execute2 = Ejecuta lo especificado [%] como un conjunto de comandos. c_exit1 = Sale de la aplicación. @@ -514,7 +503,6 @@ c_show23 = muestra las sesiones de la Base de Datos actual. c_show24 = muestra usuarios (de una Base de Datos). c_show25 = muestra copias de seguridad. -c_show26 = muestra eventos. c_store1 = Almacena datos en bruto. c_store2 = Almacena datos en bruto en la [%] especificada. c_test1 = Ejecutar los tests XQUnit. diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/build/CollectionPathTest.java basex-8.2.3/basex-core/src/test/java/org/basex/build/CollectionPathTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/build/CollectionPathTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/build/CollectionPathTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -58,7 +58,7 @@ "where $x//location contains text 'uzbekistan' " + "return $x"; try(final QueryProcessor qp = new QueryProcessor(find, context)) { - assertEquals(1, qp.execute().size()); + assertEquals(1, qp.value().size()); } } @@ -70,7 +70,7 @@ public void findDocs() throws Exception { final String find = "collection('" + NAME + "/test/zipped') "; try(final QueryProcessor qp = new QueryProcessor(find, context)) { - assertEquals(4, qp.execute().size()); + assertEquals(4, qp.value().size()); } } @@ -82,7 +82,7 @@ public void baseUri() throws Exception { final String find = "base-uri(collection('" + NAME + '/' + DIR + "xmark.xml'))"; try(final QueryProcessor qp = new QueryProcessor(find, context)) { - assertEquals(NAME + '/' + FILES[1], qp.execute().toString()); + assertEquals(NAME + '/' + FILES[1], qp.value().serialize().toString()); } } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/build/CreateTest.java basex-8.2.3/basex-core/src/test/java/org/basex/build/CreateTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/build/CreateTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/build/CreateTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -157,6 +157,6 @@ * @return first document name */ private static String docName() { - return Token.string(context.data().text(context.current().pres[0], true)); + return Token.string(context.data().text(context.current().pre(0), true)); } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/build/PathTest.java basex-8.2.3/basex-core/src/test/java/org/basex/build/PathTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/build/PathTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/build/PathTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -63,7 +63,7 @@ public void documentTestInput() throws Exception { final String count = "count(collection('" + INPUT + "/input'))"; try(final QueryProcessor qp = new QueryProcessor(count, context)) { - assertEquals(1, Integer.parseInt(qp.execute().toString())); + assertEquals(1, Integer.parseInt(qp.value().serialize().toString())); } } @@ -75,7 +75,7 @@ public void documentTestWeek() throws Exception { final String count = "count(collection('" + WEEK1 + "/week/monday'))"; try(final QueryProcessor qp = new QueryProcessor(count, context)) { - assertEquals(3, Integer.parseInt(qp.execute().toString())); + assertEquals(3, Integer.parseInt(qp.value().serialize().toString())); } } @@ -100,7 +100,7 @@ path.write(Token.token("")); context.options.set(MainOptions.PARSER, MainParser.JSON); try(final QueryProcessor qp = new QueryProcessor("doc('" + path + "')", context)) { - assertEquals("", qp.execute().toString()); + assertEquals("", qp.value().serialize().toString()); } } @@ -125,13 +125,13 @@ final String count = "count(collection('" + WEEK1 + "/week/monday')/root/monday/text[text() = 'text'])"; try(final QueryProcessor qp = new QueryProcessor(count, context)) { - assertEquals(3, Integer.parseInt(qp.execute().toString())); + assertEquals(3, Integer.parseInt(qp.value().serialize().toString())); } // cross-check final String count2 = "count(collection('" + WEEK1 + "/week')/root/monday/text[text() = 'text'])"; try(final QueryProcessor qp2 = new QueryProcessor(count2, context)) { - assertEquals(4, Integer.parseInt(qp2.execute().toString())); + assertEquals(4, Integer.parseInt(qp2.value().serialize().toString())); } } @@ -144,7 +144,7 @@ " count(collection('" + WEEK2 + "/week/tuesday')/root/monday/text[text() = 'text']) "; try(final QueryProcessor qp = new QueryProcessor(count, context)) { - assertEquals("3\n1", normNL(qp.execute())); + assertEquals("3\n1", normNL(qp.value().serialize().toString())); } } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/core/CommandLockingTest.java basex-8.2.3/basex-core/src/test/java/org/basex/core/CommandLockingTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/core/CommandLockingTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/core/CommandLockingTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -47,8 +47,6 @@ private static final StringList BACKUP_LIST = new StringList(DBLocking.BACKUP); /** StringList containing BACKUP lock string and name. */ private static final StringList BACKUP_NAME = new StringList(DBLocking.BACKUP, NAME); - /** StringList containing EVENT lock string. */ - private static final StringList EVENT_LIST = new StringList(DBLocking.EVENT); /** StringList containing java module test lock strings. */ private static final StringList MODULE_LIST = new StringList(DBLocking.MODULE_PREFIX + QueryModuleTest.LOCK1, DBLocking.MODULE_PREFIX + QueryModuleTest.LOCK2); @@ -66,14 +64,12 @@ ckDBs(new Copy(NAME2, NAME), new StringList(NAME2), NAME_LIST); ckDBs(new CreateBackup(NAME), NAME_LIST, BACKUP_LIST); ckDBs(new CreateDB(NAME), CTX_LIST, NAME_LIST); - ckDBs(new CreateEvent(NAME), true, EVENT_LIST); ckDBs(new CreateIndex(IndexType.TEXT), true, CTX_LIST); ckDBs(new CreateUser(NAME, NAME), true, ADMIN_LIST); ckDBs(new Delete(FILE), true, CTX_LIST); ckDBs(new DropBackup(NAME), true, BACKUP_LIST); ckDBs(new DropDB(NAME + '*'), true, null); // Drop using globbing ckDBs(new DropDB(NAME), true, NAME_LIST); - ckDBs(new DropEvent(NAME), true, EVENT_LIST); ckDBs(new DropIndex(IndexType.TEXT), true, CTX_LIST); ckDBs(new DropUser(NAME), true, ADMIN_LIST); ckDBs(new Execute("RUN " + FILE), true, null); @@ -107,7 +103,6 @@ ckDBs(new Run(FILE), true, null); ckDBs(new Set(NAME, NAME), false, NONE); ckDBs(new ShowBackups(), false, BACKUP_LIST); - ckDBs(new ShowEvents(), false, EVENT_LIST); ckDBs(new ShowSessions(), false, NONE); ckDBs(new ShowUsers(), false, ADMIN_LIST); ckDBs(new ShowUsers(NAME), false, ADMIN_LIST); @@ -124,6 +119,14 @@ // fn:collection() always accesses the global context, no matter what local context is ckDBs(new XQuery("/" + COUNT.args(COLLECTION.args())), false, CTX_LIST); ckDBs(new XQuery(DOC.args(NAME)), false, NAME_LIST); + ckDBs(new XQuery(ID.args(NAME)), false, CTX_LIST); + ckDBs(new XQuery(IDREF.args(NAME)), false, CTX_LIST); + ckDBs(new XQuery(ELEMENT_WITH_ID.args(NAME)), false, CTX_LIST); + ckDBs(new XQuery(LANG.args(NAME)), false, CTX_LIST); + ckDBs(new XQuery(ID.args(NAME, DOC.args(NAME))), false, NAME_LIST); + ckDBs(new XQuery(IDREF.args(NAME, DOC.args(NAME))), false, NAME_LIST); + ckDBs(new XQuery(ELEMENT_WITH_ID.args(NAME, DOC.args(NAME))), false, NAME_LIST); + ckDBs(new XQuery(LANG.args(NAME, DOC.args(NAME))), false, NAME_LIST); ckDBs(new XQuery(DOC_AVAILABLE.args(NAME + "/foo.xml")), false, NAME_LIST, null); ckDBs(new XQuery(PARSE_XML.args("")), true, NONE); ckDBs(new XQuery(PARSE_XML_FRAGMENT.args("")), true, NONE); @@ -205,7 +208,6 @@ // General Functions ckDBs(new XQuery(_DB_INFO.args(NAME)), false, NAME_LIST); ckDBs(new XQuery(_DB_LIST.args(NAME)), false, NAME_LIST); - ckDBs(new XQuery(_DB_INFO.args()), false, null); ckDBs(new XQuery(_DB_LIST_DETAILS.args(NAME)), false, NAME_LIST); ckDBs(new XQuery(_DB_LIST_DETAILS.args()), false, null); ckDBs(new XQuery(_DB_OPEN.args(NAME)), false, NAME_LIST); @@ -245,8 +247,6 @@ ckDBs(new XQuery(_DB_IS_RAW.args(NAME, FILE)), false, NAME_LIST); ckDBs(new XQuery(_DB_IS_XML.args(NAME, FILE)), false, NAME_LIST); ckDBs(new XQuery(_DB_CONTENT_TYPE.args(NAME, FILE)), false, NAME_LIST); - ckDBs(new XQuery(_DB_EVENT.args("foo", "bar")), false, NONE); - ckDBs(new XQuery(_DB_EVENT.args("foo", DOC.args(NAME))), false, NAME_LIST); } /** Test ft module. */ diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/core/CommandTest.java basex-8.2.3/basex-core/src/test/java/org/basex/core/CommandTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/core/CommandTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/core/CommandTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -413,6 +413,20 @@ ok(new Rename(FILE, "xxx")); // source need not exist ok(new Rename(FILE, "xxx")); + + // check leading and trailing slashes + ok(new CreateDB(NAME)); + ok(new Add("x.xml", "")); + ok(new Rename("x.xml", "y.xml")); + assertEquals(NAME + "/y.xml", ok(new XQuery("base-uri(.)"))); + ok(new Rename("/", "a/")); + assertEquals(NAME + "/a/y.xml", ok(new XQuery("base-uri(.)"))); + ok(new Rename("a/", "/")); + assertEquals(NAME + "/y.xml", ok(new XQuery("base-uri(.)"))); + ok(new Rename("/", "a")); + assertEquals(NAME + "/a/y.xml", ok(new XQuery("base-uri(.)"))); + ok(new Rename("a", "/")); + assertEquals(NAME + "/y.xml", ok(new XQuery("base-uri(.)"))); } /** Command test. */ diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/core/LocalConcurrencyTest.java basex-8.2.3/basex-core/src/test/java/org/basex/core/LocalConcurrencyTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/core/LocalConcurrencyTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/core/LocalConcurrencyTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -39,7 +39,7 @@ try(Session session = new LocalSession(context, "user", "")) { session.execute(new Open("store")); counter.incrementAndGet(); - } catch(Exception ex) { + } catch(final Exception ex) { error[0] = ex; } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/core/LockingTest.java basex-8.2.3/basex-core/src/test/java/org/basex/core/LockingTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/core/LockingTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/core/LockingTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -64,6 +64,21 @@ } /** + * Single thread acquires both global read lock and a single write lock. + * @throws InterruptedException Got interrupted. + */ + @Test + public void singleThreadGlobalReadLocalWriteTest() throws InterruptedException { + final CountDownLatch test = new CountDownLatch(1); + final LockTester th1 = new LockTester(null, null, objects, test); + + th1.start(); + assertTrue("Thread should be able to acquire lock.", + test.await(WAIT, TimeUnit.MILLISECONDS)); + th1.release(); + } + + /** * Test for concurrent writes. * @throws InterruptedException Got interrupted. */ diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/core/XMLCommandTest.java basex-8.2.3/basex-core/src/test/java/org/basex/core/XMLCommandTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/core/XMLCommandTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/core/XMLCommandTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -42,8 +42,6 @@ ok("X"); ok(""); - ok(""); - ok(""); ok(""); @@ -55,8 +53,6 @@ ok(""); - ok(""); - ok(""); ok(""); @@ -73,6 +69,7 @@ ok(""); + ok(""); ok(""); ok(""); @@ -126,8 +123,6 @@ ok(""); - ok(""); - ok(""); ok(""); diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/data/IndexTest.java basex-8.2.3/basex-core/src/test/java/org/basex/data/IndexTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/data/IndexTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/data/IndexTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -21,6 +21,7 @@ */ @After public void after() throws BaseXException { + run(new Close()); run(new Set(MainOptions.UPDINDEX, false)); run(new Set(MainOptions.AUTOOPTIMIZE, false)); } @@ -141,7 +142,19 @@ run(new Close()); run(new Open(NAME)); run(new Delete("A")); - run(new Close()); + } + + /** + * Test. + * @throws BaseXException database exception + */ + @Test + public void updindex8() throws BaseXException { + run(new Set(MainOptions.UPDINDEX, true)); + run(new CreateDB(NAME)); + run(new Replace("A", "")); + run(new Replace("A", "")); + run(new Replace("A", "")); } /** diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/data/UpdateTestAttributes.java basex-8.2.3/basex-core/src/test/java/org/basex/data/UpdateTestAttributes.java --- basex-8.1.1/basex-core/src/test/java/org/basex/data/UpdateTestAttributes.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/data/UpdateTestAttributes.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,7 +4,6 @@ import java.io.*; -import org.basex.data.atomic.*; import org.basex.util.*; import org.junit.*; @@ -63,7 +62,7 @@ final long nextid = data.meta.lastid; final MemData md = new MemData(context.data(), context.options); - md.attr(0, 1, data.attrNames.index(T_FOO, null, false), T_JUNIT, 0, false); + md.attr(0, 1, data.attrNames.index(T_FOO, null, false), T_JUNIT, 0); md.insert(0); data.startUpdate(context.options); data.insertAttr(9, 6, new DataClip(md)); diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/data/UpdateTestTags.java basex-8.2.3/basex-core/src/test/java/org/basex/data/UpdateTestTags.java --- basex-8.1.1/basex-core/src/test/java/org/basex/data/UpdateTestTags.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/data/UpdateTestTags.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,7 +4,6 @@ import java.io.*; -import org.basex.data.atomic.*; import org.basex.util.*; import org.junit.*; diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/data/UpdateTestText.java basex-8.2.3/basex-core/src/test/java/org/basex/data/UpdateTestText.java --- basex-8.1.1/basex-core/src/test/java/org/basex/data/UpdateTestText.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/data/UpdateTestText.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,7 +4,6 @@ import java.io.*; -import org.basex.data.atomic.*; import org.junit.*; /** diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/io/parse/json/JsonParserTest.java basex-8.2.3/basex-core/src/test/java/org/basex/io/parse/json/JsonParserTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/io/parse/json/JsonParserTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/io/parse/json/JsonParserTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -146,19 +146,6 @@ } /** - * Tests for parsing constructors. - * @throws QueryIOException query I/O exception - */ - @Test public void constructorTest() throws QueryIOException { - parse("new foo()", true); - parse("new Test(1, { })", true); - parse("new Foo([ { \"a\": new Test(1, { }) } ])", true); - - error("new" , true); - error("newt", true); - } - - /** * Tests if the given JSON string is rejected by the parser using the given spec. * @param json JSON string * @param liberal liberal parsing diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/io/parse/json/JsonStringConverter.java basex-8.2.3/basex-core/src/test/java/org/basex/io/parse/json/JsonStringConverter.java --- basex-8.1.1/basex-core/src/test/java/org/basex/io/parse/json/JsonStringConverter.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/io/parse/json/JsonStringConverter.java 2015-07-14 10:54:40.000000000 +0000 @@ -54,7 +54,7 @@ } @Override - public void openPair(final byte[] key) { + public void openPair(final byte[] key, final boolean add) { if(!first) tb.add(", "); stringLit(key); tb.add(": "); @@ -92,27 +92,6 @@ } @Override - public void openConstr(final byte[] name) { - tb.add("new ").add(name).add('('); - first = true; - } - - @Override - public void openArg() { - openItem(); - } - - @Override - public void closeArg() { - closeItem(); - } - - @Override - public void closeConstr() { - tb.add(')'); - } - - @Override public void numberLit(final byte[] value) { tb.add(value); } @@ -160,7 +139,7 @@ @Override public void nullLit() { - tb.add(Token.NULL); + tb.add(JsonConstants.NULL); } @Override diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/AdvancedQueryTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/AdvancedQueryTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/AdvancedQueryTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/AdvancedQueryTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -163,9 +163,9 @@ try(final QueryProcessor qp = new QueryProcessor(query, context)) { qp.sc.baseURI(BASEURI); try(final Serializer ser = qp.getSerializer(ao)) { - qp.execute().serialize(ser); + qp.value().serialize(ser); } } - return normNL(ao); + return normNL(ao.toString()); } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/ast/GFLWOROptimizeTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/ast/GFLWOROptimizeTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/ast/GFLWOROptimizeTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/ast/GFLWOROptimizeTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -256,4 +256,14 @@ "exists(//Let)" ); } + + /** Checks that clauses can be removed during inlining. */ + @Test public void gh1150() { + check("for $i in 1 to xs:integer(random:double()) let $x := 'does-not-exist.xml' " + + "for $x in doc($x) let $x := count($x) return $x", + "", + "count(//For) eq 1", + "count(//GFLWOR/*) eq 2", + "starts-with(//GFLWOR/*[last()]/@name, 'error(')"); + } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/ast/InlineTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/ast/InlineTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/ast/InlineTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/ast/InlineTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -133,4 +133,14 @@ + "let $b := $a(1) let $c := $a(1) let $d := $a(1) return $b", "1", "count(//Let) != 2"); check("let $a := 'foo' return 'bar' ! . ! $a", "foo", "empty(//Let)"); } + + /** Checks that window clauses are recognized as loops. */ + @Test public void gh1126() { + check("let $s := (1 to 2) ! . " + + "for tumbling window $w in 1 to 2 start when true() end when true() return $s", + "1\n2\n1\n2", + "count(//Let) eq 1", + "count(//Window) eq 1", + "//Let << //Window"); + } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/ast/QueryPlanTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/ast/QueryPlanTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/ast/QueryPlanTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/ast/QueryPlanTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -28,7 +28,7 @@ // retrieve compiled query plan final FDoc plan = qp.plan(); // compare results - if(res != null) assertEquals(res, normNL(qp.execute())); + if(res != null) assertEquals(res, normNL(qp.value().serialize().toString())); for(final String p : pr) { if(new QueryProcessor(p, context).context(plan).value() != Bln.TRUE) { @@ -36,7 +36,7 @@ "- Plan: " + plan.serialize()); } } - } catch(final QueryException | QueryIOException ex) { + } catch(final Exception ex) { final AssertionError err = new AssertionError(Util.message(ex)); err.initCause(ex); throw err; diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/ast/RewritingsTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/ast/RewritingsTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/ast/RewritingsTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/ast/RewritingsTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -190,6 +190,21 @@ check("[" + STRING_LENGTH.args() + " gt 1]", "", "exists(//" + stringLength + ")"); check("[" + STRING_LENGTH.args() + " = 1]", "", "exists(//" + stringLength + ")"); + } + + /** + * Checks that empty sequences are eliminated from lists and that singleton lists are flattened. + */ + @Test + public void list() { + check("((), , ())", "", "empty(//List)", "empty(//Empty)", "exists(/*/CElem)"); + } + /** + * Checks that expressions marked as non-deterministic will not be rewritten. + */ + @Test + public void nonDeterministic() { + check("count((# basex:non-deterministic #) { })", "1", "exists(//FnCount)"); } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/expr/PackageAPITest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/expr/PackageAPITest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/expr/PackageAPITest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/expr/PackageAPITest.java 2015-07-14 10:54:40.000000000 +0000 @@ -49,7 +49,7 @@ for(final IOFile f : new IOFile(REPO).children()) { if(f.isDir() && f.name().contains(".")) f.delete(); } - context = newContext(); + context = new Context(); context.soptions.set(StaticOptions.REPOPATH, REPO); } @@ -204,16 +204,16 @@ /** * Tests ability to import two modules from the same package. - * @throws QueryException query exception + * @throws Exception exception */ @Test - public void importTwoModulesFromPkg() throws QueryException { + public void importTwoModulesFromPkg() throws Exception { try(final QueryProcessor qp = new QueryProcessor( "import module namespace ns1='ns1';" + "import module namespace ns3='ns3';" + "(ns1:test2() eq 'pkg2mod1') and (ns3:test() eq 'pkg2mod2')", context)) { - assertEquals(qp.execute().toString(), "true"); + assertEquals(qp.value().serialize().toString(), "true"); } } @@ -248,11 +248,10 @@ /** * Tests installing of a package containing a jar file. - * @throws BaseXException database exception - * @throws QueryException query exception + * @throws Exception exception */ @Test - public void installJar() throws BaseXException, QueryException { + public void installJar() throws Exception { // install package new RepoInstall(REPO + "testJar.xar", null).execute(context); @@ -268,7 +267,7 @@ // use package try(final QueryProcessor qp = new QueryProcessor( "import module namespace j='jar'; j:print('test')", context)) { - assertEquals(qp.execute().toString(), "test"); + assertEquals(qp.value().serialize().toString(), "test"); } // delete package @@ -277,19 +276,19 @@ /** * Tests usage of installed packages. - * @throws QueryException query exception + * @throws Exception exception */ @Test - public void importPkg() throws QueryException { + public void importPkg() throws Exception { // try with a package without dependencies try(final QueryProcessor qp = new QueryProcessor( "import module namespace ns3='ns3'; ns3:test()", context)) { - assertEquals(qp.execute().toString(), "pkg2mod2"); + assertEquals(qp.value().serialize().toString(), "pkg2mod2"); } // try with a package with dependencies try(final QueryProcessor qp = new QueryProcessor( "import module namespace ns2='ns2'; ns2:test()", context)) { - assertEquals(qp.execute().toString(), "pkg2mod2"); + assertEquals(qp.value().serialize().toString(), "pkg2mod2"); } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/FTIndexQueryTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/FTIndexQueryTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/FTIndexQueryTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/FTIndexQueryTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -23,9 +23,9 @@ * @throws BaseXException database exception */ private static void init(final String input) throws BaseXException { - new CreateDB(NAME, input).execute(SandboxTest.context); - new Set(MainOptions.FTINDEX, true).execute(SandboxTest.context); - new CreateDB(NAME + "ix", input).execute(SandboxTest.context); + new CreateDB(NAME, input).execute(Sandbox.context); + new Set(MainOptions.FTINDEX, true).execute(Sandbox.context); + new CreateDB(NAME + "ix", input).execute(Sandbox.context); } /** diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/func/AdminModuleTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/func/AdminModuleTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/func/AdminModuleTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/func/AdminModuleTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -56,7 +56,7 @@ @Test public void deleteLogs() { // no logging data exists in the sandbox - error(_ADMIN_DELETE_LOGS.args(Log.name(new Date())), BXCA_TODAY); + error(_ADMIN_DELETE_LOGS.args(Log.name(new Date())), BXAD_TODAY); error(_ADMIN_DELETE_LOGS.args("2001-01-01"), WHICHRES_X); } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/func/ArchiveModuleTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/func/ArchiveModuleTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/func/ArchiveModuleTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/func/ArchiveModuleTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -18,33 +18,32 @@ private static final String ZIP = "src/test/resources/xml.zip"; /** Test GZIP file. */ private static final String GZIP = "src/test/resources/xml.gz"; + /** Test file. */ + private static final String DIR = "src/test/resources/dir"; /** Test method. */ @Test public void create() { // simple zip files - query(COUNT.args(_ARCHIVE_CREATE.args("X", "")), "1"); + count(_ARCHIVE_CREATE.args("X", ""), 1); + // simple zip files - query(COUNT.args(_ARCHIVE_CREATE.args("X", "")), "1"); - query(COUNT.args(_ARCHIVE_CREATE.args( - "X", "")), "1"); - query(COUNT.args(_ARCHIVE_CREATE.args( - "X", "")), - "1"); - query(COUNT.args(_ARCHIVE_CREATE.args("X", "")), "1"); - query(COUNT.args(_ARCHIVE_CREATE.args("X", "", - " map { }")), "1"); - query(COUNT.args(_ARCHIVE_CREATE.args("X", "", - " map { 'format':'zip', 'algorithm':'deflate' }")), "1"); - query(COUNT.args(_ARCHIVE_CREATE.args("X", "", "")), "1"); - query(COUNT.args(_ARCHIVE_CREATE.args("X", "", + count(_ARCHIVE_CREATE.args("X", ""), 1); + count(_ARCHIVE_CREATE.args("X", ""), 1); + count(_ARCHIVE_CREATE.args("X", ""), 1); + count(_ARCHIVE_CREATE.args("X", ""), 1); + count(_ARCHIVE_CREATE.args("X", "", " map { }"), 1); + count(_ARCHIVE_CREATE.args("X", "", + " map { 'format':'zip', 'algorithm':'deflate' }"), 1); + count(_ARCHIVE_CREATE.args("X", "", ""), 1); + count(_ARCHIVE_CREATE.args("X", "", "" + - "")), "1"); - query(COUNT.args(_ARCHIVE_CREATE.args("X", "", - "")), "1"); - query(COUNT.args(_ARCHIVE_CREATE.args("X", "", - "")), "1"); + ""), 1); + count(_ARCHIVE_CREATE.args("X", "", + ""), 1); + count(_ARCHIVE_CREATE.args("X", "", + ""), 1); // different number of entries and contents error(_ARCHIVE_CREATE.args("X", "()"), ARCH_DIFF_X_X); @@ -73,29 +72,41 @@ error(_ARCHIVE_CREATE.args("X", "", " map { 'x':'y' }"), INVALIDOPT_X); error(_ARCHIVE_CREATE.args("X", "", - ""), - ARCH_UNKNOWN); + ""), ARCH_UNKNOWN); // algorithm not supported error(_ARCHIVE_CREATE.args("X", "", - ""), - ARCH_SUPP_X_X); + ""), ARCH_SUPP_X_X); // algorithm not supported error(_ARCHIVE_CREATE.args("('x','y')", "('a','b')", - ""), - ARCH_ONE_X); + ""), ARCH_ONE_X); + } + + /** Test method. */ + @Test + public void createFrom() { + count(_ARCHIVE_CREATE_FROM.args(DIR), 4); + count(_ARCHIVE_CREATE_FROM.args(DIR, " map { }"), 4); + count(_ARCHIVE_CREATE_FROM.args(DIR, " map { 'algorithm': 'stored' }"), 4); + query(PARSE_XML.args(_ARCHIVE_EXTRACT_TEXT.args(_ARCHIVE_CREATE_FROM.args(DIR, " map { }"), + "input.xml")) + " instance of document-node()", "true"); + + // errors + error(_ARCHIVE_CREATE_FROM.args("UNUNUNKNOWN"), FILE_NO_DIR_X); + error(_ARCHIVE_CREATE_FROM.args(DIR, " map { }", "UNUNUNKNOWN"), FILE_NOT_FOUND_X); + error(_ARCHIVE_CREATE_FROM.args(DIR, " map { }", "."), FILE_IS_DIR_X); } /** Test method. */ @Test public void entries() { // read entries - query(COUNT.args(_ARCHIVE_ENTRIES.args(_FILE_READ_BINARY.args(ZIP))), "5"); + query(COUNT.args(_ARCHIVE_ENTRIES.args(_FILE_READ_BINARY.args(ZIP))), 5); // simple zip files query(COUNT.args(_ARCHIVE_ENTRIES.args(_FILE_READ_BINARY.args(ZIP)) + - "[@size][@last-modified][@compressed-size]"), "5"); + "[@size][@last-modified][@compressed-size]"), 5); // simple gzip files query(COUNT.args(_ARCHIVE_ENTRIES.args(_FILE_READ_BINARY.args(GZIP)) + - "[not(@size)][not(@last-modified)][not(@compressed-size)][not(text())]"), "1"); + "[not(@size)][not(@last-modified)][not(@compressed-size)][not(text())]"), 1); } /** Test method. */ @@ -112,7 +123,7 @@ @Test public void extractText() { // extract all entries - query(COUNT.args(_ARCHIVE_EXTRACT_TEXT.args(_FILE_READ_BINARY.args(ZIP))), "5"); + query(COUNT.args(_ARCHIVE_EXTRACT_TEXT.args(_FILE_READ_BINARY.args(ZIP))), 5); // extract all entries query("let $a := " + _FILE_READ_BINARY.args(ZIP) + "let $b := " + _ARCHIVE_ENTRIES.args("$a") + @@ -134,7 +145,7 @@ @Test public void extractBinary() { // extract all entries - query(COUNT.args(_ARCHIVE_EXTRACT_BINARY.args(_FILE_READ_BINARY.args(ZIP))), "5"); + query(COUNT.args(_ARCHIVE_EXTRACT_BINARY.args(_FILE_READ_BINARY.args(ZIP))), 5); // extract all entries query("let $a := " + _FILE_READ_BINARY.args(ZIP) + "let $b := " + _ARCHIVE_ENTRIES.args("$a") + @@ -156,6 +167,26 @@ /** Test method. */ @Test + public void extractTo() { + // write archive and count number of entries + final String tmp = new IOFile(sandbox(), "tmp").path(); + query(_ARCHIVE_EXTRACT_TO.args(tmp, _FILE_READ_BINARY.args(ZIP))); + count(_FILE_READ_BINARY.args(ZIP), 5); + // write archive and count number of entries + query(_ARCHIVE_EXTRACT_TO.args(tmp, _FILE_READ_BINARY.args(ZIP), + _ARCHIVE_ENTRIES.args(_FILE_READ_BINARY.args(ZIP)))); + count(_FILE_READ_BINARY.args(ZIP), 5); + + query("let $a := " + _ARCHIVE_ENTRIES.args( + _FILE_READ_BINARY.args(ZIP)) + "/string() " + + "let $f := " + _FILE_LIST.args(tmp, "true()") + '[' + + _FILE_IS_FILE.args(" '" + tmp + "/'||.") + "] ! replace(., '\\\\', '/') " + + "return (every $e in $a satisfies $e = $f) and (every $e in $f satisfies $e =$ a)", + "true"); + } + + /** Test method. */ + @Test public void update() { // add a new entry query(_FILE_READ_BINARY.args(ZIP) + " ! " + @@ -197,23 +228,12 @@ _ARCHIVE_DELETE.args(" .", "X"), ARCH_MODIFY_X); } - /** Test method. */ - @Test - public void write() { - // write archive and count number of entries - final String tmp = new IOFile(sandbox(), "tmp").path(); - query(_ARCHIVE_WRITE.args(tmp, _FILE_READ_BINARY.args(ZIP))); - query(COUNT.args(_ARCHIVE_ENTRIES.args(_FILE_READ_BINARY.args(ZIP))), "5"); - // write archive and count number of entries - query(_ARCHIVE_WRITE.args(tmp, _FILE_READ_BINARY.args(ZIP), - _ARCHIVE_ENTRIES.args(_FILE_READ_BINARY.args(ZIP)))); - query(COUNT.args(_ARCHIVE_ENTRIES.args(_FILE_READ_BINARY.args(ZIP))), "5"); - - query("let $a := " + _ARCHIVE_ENTRIES.args( - _FILE_READ_BINARY.args(ZIP)) + "/string() " + - "let $f := " + _FILE_LIST.args(tmp, "true()") + '[' + - _FILE_IS_FILE.args(" '" + tmp + "/'||.") + "] ! replace(., '\\\\', '/') " + - "return (every $e in $a satisfies $e = $f) and (every $e in $f satisfies $e =$ a)", - "true"); + /** + * Counts the entries of an archive. + * @param archive archive + * @param exp expected number of results + */ + private void count(final String archive, final int exp) { + query(COUNT.args(_ARCHIVE_ENTRIES.args(archive)), exp); } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/func/DbModuleTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/func/DbModuleTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/func/DbModuleTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/func/DbModuleTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -189,7 +189,7 @@ query(xmlCall + "/@raw/data()", "false"); query(xmlCall + "/@content-type/data()", MediaType.APPLICATION_XML.toString()); query(xmlCall + "/@modified-date/xs:dateTime(.)"); - query(xmlCall + "/@size/data()", ""); + query(xmlCall + "/@size/data()", "2"); query(xmlCall + "/text()", "xml"); final String rawCall = _DB_LIST_DETAILS.args(NAME, "raw"); @@ -212,6 +212,7 @@ new CreateBackup(NAME).execute(context); query(COUNT.args(_DB_BACKUPS.args()), "1"); query(COUNT.args(_DB_BACKUPS.args(NAME)), "1"); + query(COUNT.args(_DB_BACKUPS.args(NAME) + "/(@database, @date, @size)"), "3"); query(COUNT.args(_DB_BACKUPS.args(NAME + 'X')), "0"); new DropBackup(NAME).execute(context); query(COUNT.args(_DB_BACKUPS.args(NAME)), "0"); @@ -246,12 +247,6 @@ /** Test method. */ @Test - public void event() { - error(_DB_EVENT.args("X", "Y"), BXDB_EVENT_X); - } - - /** Test method. */ - @Test public void output() { query(_DB_OUTPUT.args("x"), "x"); query(_DB_OUTPUT.args("('x','y')"), "x\ny"); @@ -263,6 +258,13 @@ /** Test method. */ @Test + public void outputCache() { + query(_DB_OUTPUT_CACHE.args(), ""); + query(_DB_OUTPUT.args("x") + ',' + _DB_OUTPUT.args(_DB_OUTPUT_CACHE.args()), "x\nx"); + } + + /** Test method. */ + @Test public void add() { query(COUNT.args(COLLECTION.args(NAME)), "1"); query(_DB_ADD.args(NAME, FILE)); @@ -687,6 +689,7 @@ public void path() { query(_DB_PATH.args(_DB_OPEN.args(NAME)), FILE.replaceAll(".*/", "")); query(_DB_PATH.args(_DB_OPEN.args(NAME) + "/*"), FILE.replaceAll(".*/", "")); + query(_DB_PATH.args(" update ()"), ""); } /** diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/func/FileModuleTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/func/FileModuleTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/func/FileModuleTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/func/FileModuleTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -124,6 +124,13 @@ /** Test method. */ @Test + public void isAbsolute() { + query(_FILE_IS_ABSOLUTE.args(PATH), true); + query(_FILE_IS_ABSOLUTE.args("a"), false); + } + + /** Test method. */ + @Test public void isFile() { query(_FILE_IS_FILE.args(PATH), false); query(_FILE_WRITE.args(PATH1, "()")); @@ -426,9 +433,15 @@ @Test public void resolvePath() { final String path = query(_FILE_RESOLVE_PATH.args(PATH1)); - final String can = Paths.get(PATH1).normalize().toAbsolutePath().toString(); - assertEquals(path, can); + final String can1 = Paths.get(PATH1).normalize().toAbsolutePath().toString(); + final String can2 = Paths.get(PATH2).normalize().toAbsolutePath().toString(); + assertEquals(path, can1); query(ENDS_WITH.args(_FILE_RESOLVE_PATH.args("."), File.separator), "true"); + + query(CONTAINS.args(_FILE_RESOLVE_PATH.args(can1, can2), can1), "true"); + query(CONTAINS.args(_FILE_RESOLVE_PATH.args("X", can1), can1 + File.separator + "X"), "true"); + error(_FILE_RESOLVE_PATH.args(can1, "b"), FILE_IS_RELATIVE_X); + error(_FILE_RESOLVE_PATH.args("X", "b"), FILE_IS_RELATIVE_X); } /** Test method. */ diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/func/fn/FnFormatNumberTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/func/fn/FnFormatNumberTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/func/fn/FnFormatNumberTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/func/fn/FnFormatNumberTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -42,7 +42,7 @@ { "formnum 10", strings("0"), "format-number(0, '0')" }, { "formnum 20", strings("00"), "format-number(0, '10')" }, { "formnum 30", strings("1"), "format-number(1, '0')" }, - { "formnum 35", strings("0.1"), "format-number(0.1, '.0')" }, + { "formnum 35", strings(".1"), "format-number(0.1, '.0')" }, { "formnum 40", strings("1.0"), "format-number(1, '1.0')" }, { "formnum 50", strings("1"), "format-number(1.1, '1')" }, { "formnum 60", strings("1.1"), "format-number(1.1, '1.0')" }, @@ -73,7 +73,14 @@ "format-number(xs:decimal('11111111111111111111'), '#,#')" }, { "formnum 300", strings("12.346e2"), "format-number(1234.5678, '00.000e0')" }, - // http://www.w3schools.com/XSL/func_formatnumber.asp + { "formnum 310", strings("\u0a66,i"), + "declare default decimal-format zero-digit = '੦';" + + "format-number(0, '#,i')" }, + { "formnum 320", strings("\u0a67"), + "declare default decimal-format zero-digit = '੦';" + + "format-number(1, '#\u0a66')" }, + + // http://www.w3schools.com/XSL/func_formatnumber.asp { "formnum w3-10", strings("500100"), "format-number(500100, '#')" }, { "formnum w3-20", strings("500100"), "format-number(500100, '0')" }, { "formnum w3-30", strings("500100.00"), "format-number(500100, '#.00')" }, diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/func/fn/FnTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/func/fn/FnTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/func/fn/FnTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/func/fn/FnTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -34,12 +34,25 @@ /** Test method. */ @Test + public void jsonToXml() { + contains(JSON_TO_XML.args("null"), "xmlns"); + contains(JSON_TO_XML.args("null") + " update ()", "xmlns"); + } + + /** Test method. */ + @Test public void serialize() { contains(SERIALIZE.args(""), ""); contains(SERIALIZE.args("", serialParams("")), ""); contains(SERIALIZE.args("a", serialParams("")), "a"); } + /** Test method. */ + @Test + public void parseSubstring() { + contains(SUBSTRING.args("'ab'", " [2]"), "b"); + } + /** Tests for the {@code replace} function. */ @Test public void replace() { @@ -65,6 +78,14 @@ error("sum((), (1,2))", SEQFOUND_X); } + /** Tests for the {@code static-base-uri} function. */ + @Test + public void staticBaseURI() { + query("declare base-uri 'a/'; " + ENDS_WITH.args(STATIC_BASE_URI.args(), "/"), "true"); + query("declare base-uri '.'; " + ENDS_WITH.args(STATIC_BASE_URI.args(), "/"), "true"); + query("declare base-uri '..'; " + ENDS_WITH.args(STATIC_BASE_URI.args(), "/"), "true"); + } + /** Tests for the {@code parse-ietf-date} function. */ @Test public void parseIetfDate() { diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/func/FtModuleTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/func/FtModuleTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/func/FtModuleTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/func/FtModuleTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -223,5 +223,7 @@ query("declare ft-option using stemming; " + _FT_NORMALIZE.args("Gifts"), "gift"); query(_FT_NORMALIZE.args(""), ""); query(_FT_NORMALIZE.args("a!b:c"), "a!b:c"); + + query("ft:normalize('̊', map { 'stemming': true(), 'language': 'de' })", ""); } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/func/InspectModuleTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/func/InspectModuleTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/func/InspectModuleTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/func/InspectModuleTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -48,9 +48,11 @@ query(func + "/return/@type/data()", "xs:integer"); query(func + "/return/@occurrence/data()", ""); - // unknown annotations disappear - query("declare namespace x='x';" + - _INSPECT_FUNCTION.args(" %x:x function() {()}") + "/annotation", ""); + // unknown annotation + query("declare namespace pref='uri';" + + _INSPECT_FUNCTION.args(" %pref:x function() {()}") + "/annotation/@name/data()", "pref:x"); + query("declare namespace pref='uri';" + + _INSPECT_FUNCTION.args(" %pref:x function() {()}") + "/annotation/@uri/data()", "uri"); } /** Test method. */ @@ -59,18 +61,26 @@ error(_INSPECT_MODULE.args("src/test/resources/non-existent.xqm"), WHICHRES_X); final String module = "src/test/resources/hello.xqm"; - final String result = query(_INSPECT_MODULE.args(module)); + final String result = query(_INSPECT_MODULE.args(module)). + replace("{", "{{").replace("}", "}}"); + final String var = query(result + "/variable[@name = 'hello:lazy']"); query(var + "/@uri/data()", "world"); query(var + "/annotation/@name/data()", "basex:lazy"); query(var + "/annotation/@uri/data()", "http://basex.org"); - final String func = query(result + "/function[@name = 'hello:world']"); - query(func + "/@uri/data()", "world"); - query(func + "/annotation/@name/data()", "public"); - query(func + "/annotation/@uri/data()", "http://www.w3.org/2012/xquery"); - query(func + "/return/@type/data()", "xs:string"); - query(func + "/return/@occurrence/data()", ""); + final String func1 = query(result + "/function[@name = 'hello:world']"); + query(func1 + "/@uri/data()", "world"); + query(func1 + "/annotation/@name/data()", "public"); + query(func1 + "/annotation/@uri/data()", "http://www.w3.org/2012/xquery"); + query(func1 + "/return/@type/data()", "xs:string"); + query(func1 + "/return/@occurrence/data()", ""); + + final String func2 = query(result + "/function[@name = 'hello:internal']"). + replace("{", "{{").replace("}", "}}");; + query(func2 + "/@uri/data()", "world"); + query(func2 + "/annotation/@name/data()[ends-with(., 'ignored')]", "ignored"); + query(func2 + "/annotation/@uri/data()[. = 'ns']", "ns"); } /** Test method. */ @@ -79,7 +89,8 @@ error(_INSPECT_XQDOC.args("src/test/resources/non-existent.xqm"), WHICHRES_X); // validate against xqDoc schema - final String result = query(_INSPECT_XQDOC.args("src/test/resources/hello.xqm")); + final String result = query(_INSPECT_XQDOC.args("src/test/resources/hello.xqm")). + replace("{", "{{").replace("}", "}}"); query(_VALIDATE_XSD.args(result, "src/test/resources/xqdoc.xsd")); } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/func/WebModuleTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/func/WebModuleTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/func/WebModuleTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/func/WebModuleTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -73,4 +73,22 @@ query(_WEB_RESPONSE_HEADER.args(" map { }", " map { 'Cache-Control': '' }") + "/*:response/*:header", ""); } + + /** Test method. */ + @Test + public void encodeUrl() { + query(_WEB_ENCODE_URL.args("a *-._"), "a%0D+*-._"); + } + + /** Test method. */ + @Test + public void decodeUrl() { + query(_WEB_DECODE_URL.args("a+-._*"), "a -._*"); + query("let $s := codepoints-to-string((9, 10, 13, 32 to 55295, 57344 to 65533, 65536)) " + + "return $s = web:decode-url(web:encode-url($s))", "true"); + + error(_WEB_DECODE_URL.args("%1"), BXWE_INVALID_X); + error(_WEB_DECODE_URL.args("%1F"), BXWE_CODES_X); + error(_WEB_DECODE_URL.args("%D8%00"), BXWE_CODES_X); + } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/IndexOptimizeTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/IndexOptimizeTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/IndexOptimizeTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/IndexOptimizeTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -258,7 +258,7 @@ String plan = null; try { try(QueryProcessor qp = new QueryProcessor(query, context)) { - final String string = qp.execute().serialize(); + final String string = qp.value().serialize().toString(); if(result != null) assertEquals(result, normNL(string)); // fetch query plan @@ -269,7 +269,7 @@ try(QueryProcessor qp = new QueryProcessor(plan + "/descendant-or-self::*" + "[self::" + Util.className(ValueAccess.class) + "|self::" + Util.className(FTIndexAccess.class) + ']', context)) { - final String string = qp.execute().serialize(); + final String string = qp.value().serialize().toString(); assertFalse("No index used:\n- Query: " + query + "\n- Plan: " + plan + "\n- " + qp.info().trim(), string.isEmpty()); } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/ModuleTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/ModuleTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/ModuleTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/ModuleTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,6 +5,8 @@ import org.basex.*; import org.basex.core.cmd.*; import org.basex.io.*; +import org.basex.query.util.*; +import org.basex.query.value.item.*; import org.basex.util.*; import org.junit.Test; @@ -23,7 +25,7 @@ public void builtIn() throws Exception { final String query = "import module namespace xquery = 'http://basex.org/modules/xquery'; 1"; try(final QueryProcessor qp = new QueryProcessor(query, context)) { - qp.execute(); + qp.value(); } } @@ -81,4 +83,22 @@ qc.parseMain("import module namespace a='a'; ()", null, null); } } + + /** + * Uses a URI resolver. + * @throws Exception exception + */ + @Test + public void uriResolver() throws Exception { + final String query = "import module namespace m='uri' at 'x.xq'; m:f()"; + try(final QueryProcessor qp = new QueryProcessor(query, context)) { + qp.uriResolver(new UriResolver() { + @Override + public IO resolve(final String path, final String uri, final Uri base) { + return new IOContent("module namespace m='uri'; declare function m:f() { 'OK' };"); + } + }); + assertEquals(qp.value().serialize().toString(), "OK"); + } + } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/NamespaceTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/NamespaceTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/NamespaceTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/NamespaceTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,6 +7,7 @@ import org.basex.core.*; import org.basex.core.cmd.*; +import org.basex.data.*; import org.basex.io.*; import org.basex.io.serial.*; import org.basex.query.up.primitives.node.*; @@ -60,8 +61,8 @@ create(12); query("insert node into doc('d12')/*:a/*:b"); assertEquals(NL + - " Pre[3] xmlns:ns=\"A\" " + NL + - " Pre[4] xmlns=\"B\" ", + " Pre[3] xmlns:ns=\"A\"" + NL + + " Pre[4] xmlns=\"B\"", context.data().nspaces.toString()); } @@ -75,7 +76,7 @@ create(13); query("insert node as first into doc('d13')/a"); assertEquals(NL + - " Pre[3] xmlns=\"A\" ", + " Pre[3] xmlns=\"A\"", context.data().nspaces.toString()); } @@ -89,10 +90,10 @@ create(14); query("insert node into doc('d14')/*:a/*:b"); assertEquals(NL + - " Pre[1] xmlns=\"A\" " + NL + - " Pre[2] xmlns=\"B\" " + NL + - " Pre[3] xmlns=\"D\" " + NL + - " Pre[4] xmlns=\"C\" ", + " Pre[1] xmlns=\"A\"" + NL + + " Pre[2] xmlns=\"B\"" + NL + + " Pre[3] xmlns=\"D\"" + NL + + " Pre[4] xmlns=\"C\"", context.data().nspaces.toString()); } @@ -106,7 +107,7 @@ create(12); query("delete node doc('d12')/a/b"); assertEquals(NL + - " Pre[2] xmlns=\"B\" ", + " Pre[2] xmlns=\"B\"", context.data().nspaces.toString()); } @@ -120,8 +121,8 @@ create(14); query("delete node doc('d14')/*:a/*:b"); assertEquals(NL + - " Pre[1] xmlns=\"A\" " + NL + - " Pre[2] xmlns=\"C\" ", + " Pre[1] xmlns=\"A\"" + NL + + " Pre[2] xmlns=\"C\"", context.data().nspaces.toString()); } @@ -135,9 +136,9 @@ create(15); query("delete node doc('d15')/*:a/*:c"); assertEquals(NL + - " Pre[1] xmlns=\"A\" " + NL + - " Pre[2] xmlns=\"B\" " + NL + - " Pre[3] xmlns=\"E\" ", + " Pre[1] xmlns=\"A\"" + NL + + " Pre[2] xmlns=\"B\"" + NL + + " Pre[3] xmlns=\"E\"", context.data().nspaces.toString()); } @@ -154,6 +155,23 @@ } /** + * Inserts an attribute with namespace. + * @throws Exception exception + */ + @Test + public void insertAttributeWithNs() throws Exception { + create(1); + query("insert node attribute { QName('ns', 'pref:local') } { } into /*"); + final Data data = context.data(); + assertEquals(false, data.nsFlag(0)); + assertEquals(true, data.nsFlag(1)); + assertEquals(false, data.nsFlag(2)); + assertEquals(0, data.uriId(1, data.kind(1))); + assertEquals(1, data.uriId(2, data.kind(2))); + assertEquals("ns", string(data.nspaces.uri(1))); + } + + /** * Tests for correct namespace hierarchy, esp. if namespace nodes * on the following axis of an insert/delete operation are * updated correctly. @@ -176,7 +194,7 @@ create(21); query("delete node //b"); assertEquals(NL + - " Pre[2] xmlns:p1=\"u1\" ", + " Pre[2] xmlns:p1=\"u1\"", context.data().nspaces.toString()); } @@ -264,9 +282,9 @@ create(9); query("insert node into doc('d9')//*:e"); assertEquals(NL + - " Pre[1] xmlns=\"A\" " + NL + - " Pre[4] xmlns=\"D\" " + NL + - " Pre[6] xmlns=\"F\" ", + " Pre[1] xmlns=\"A\"" + NL + + " Pre[4] xmlns=\"D\"" + NL + + " Pre[6] xmlns=\"F\"", context.data().nspaces.toString()); } @@ -279,10 +297,10 @@ create(10); query("insert node into doc('d10')//*:e"); assertEquals(NL + - " Pre[1] xmlns=\"A\" " + NL + - " Pre[4] xmlns=\"D\" " + NL + - " Pre[5] xmlns=\"G\" " + NL + - " Pre[7] xmlns=\"F\" ", + " Pre[1] xmlns=\"A\"" + NL + + " Pre[4] xmlns=\"D\"" + NL + + " Pre[5] xmlns=\"G\"" + NL + + " Pre[7] xmlns=\"F\"", context.data().nspaces.toString()); } @@ -301,8 +319,8 @@ create(2); query("insert node into doc('d2')//*:x"); assertEquals(NL + - " Pre[1] xmlns=\"xx\" " + NL + - " Pre[2] xmlns=\"y\" ", + " Pre[1] xmlns=\"xx\"" + NL + + " Pre[2] xmlns=\"y\"", context.data().nspaces.toString()); } @@ -482,7 +500,7 @@ context.data().startUpdate(context.options); context.data().delete(0); context.data().finishUpdate(context.options); - final byte[] ns = context.data().nspaces.globalNS(); + final byte[] ns = context.data().nspaces.globalUri(); assertTrue(ns != null && ns.length == 0); } @@ -630,7 +648,7 @@ query( "let $b := " + "return insert node $b//@*:id into /*:n"); - assertEquals(1, context.data().ns(1).size()); + assertEquals(1, context.data().namespaces(1).size()); } /** Handles duplicate prefixes. */ @@ -747,7 +765,7 @@ " $target := $input-context/works[1]/employee[1]" + "return insert nodes $source into $target"; try(final QueryProcessor qp = new QueryProcessor(query, context)) { - qp.execute(); + qp.value(); } catch(final QueryException ex) { assertEquals("XUTY0004", string(ex.qname().local())); } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/ParallelQueryTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/ParallelQueryTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/ParallelQueryTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/ParallelQueryTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -40,11 +40,11 @@ /** * Runs a single query. * @return result - * @throws QueryException exception + * @throws Exception exception */ - private static String query() throws QueryException { + private static String query() throws Exception { try(final QueryProcessor qp = new QueryProcessor(QUERY, context)) { - return qp.value().toString(); + return qp.value().serialize().toString(); } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/QueryTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/QueryTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/QueryTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/QueryTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -44,9 +44,9 @@ String s = correct && cmp.size() != 1 ? "#" + cmp.size() : ""; sb.append("\n[E" + s + "] "); if(correct) { - final String cp = cmp.toString(); + final String cp = cmp.serialize().toString(); sb.append('\''); - sb.append(cp.length() > 1000 ? cp.substring(0, 1000) + "..." : cp); + sb.append(cp.length() > 5000 ? cp.substring(0, 5000) + "..." : cp); sb.append('\''); } else { sb.append("error"); diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/SerializerTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/SerializerTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/SerializerTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/SerializerTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,7 +5,7 @@ import org.junit.*; /** - * This class tests serializer. + * This class tests the serializers. * * @author BaseX Team 2005-15, BSD License * @author Christian Gruen @@ -25,7 +25,10 @@ query(option + "", ""); final String[] empties = { "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "basefont", "frame", "isindex", "param" }; - for(final String e : empties) query(option + '<' + e + "/>", '<' + e + " />"); + for(final String e : empties) { + query(option + "<" + e + "/>", + "\n<" + e + " />\n"); + } } /** Test: method=html. */ diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/simple/SimpleTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/simple/SimpleTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/simple/SimpleTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/simple/SimpleTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -158,6 +158,11 @@ { "Catch 6", integers(1), "declare function local:f($x) { try { 1 idiv $x } catch * { 1 } }; local:f(0)" }, + { "NodeTest 1", strings("a"), + "let $d as document-node(element()) := parse-xml('') return name($d/*)" }, + { "NodeTest 2", strings("a"), + "let $d as document-node(element(a)) := parse-xml('') return name($d/*)" }, + { "FuncTest 1", integers(1), "xquery version '1.0';" + "declare function local:foo() { count(local:bar()) };" + "declare function local:bar() { 42 };" + @@ -196,6 +201,11 @@ { "Map 3", strings("a", "b"), "/b ! ancestor-or-self::node() ! name()" }, { "Constructor 1", strings("1"), "{attribute{'a'}{1}}/@a/string()" }, + + // #1140 + { "Pred 1", empty(), "declare function local:test() {" + + "for $n in (1, 1) return <_>/*[$n[1]] }; local:test()/self::w" }, + { "Pred 2", empty(), "for $n in (2,2) return (, )/*[$n[$n]]" } }; } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/up/AtomicUpdatesTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/up/AtomicUpdatesTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/up/AtomicUpdatesTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/up/AtomicUpdatesTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,9 +7,9 @@ import org.basex.build.*; import org.basex.data.*; -import org.basex.data.atomic.*; import org.basex.io.*; import org.basex.query.*; +import org.basex.query.up.atomic.*; import org.basex.util.*; import org.junit.*; import org.junit.rules.*; @@ -527,7 +527,7 @@ */ private static DataClip clipA(final Data d, final String name, final String value) { final int s = d.meta.size; - d.attr(s, s + 1, d.attrNames.index(token(name), null, false), token(value), -1, false); + d.attr(s, s + 1, d.attrNames.index(token(name), null, false), token(value), -1); d.insert(s); return new DataClip(d, s, d.meta.size); } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/up/UpdateTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/up/UpdateTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/up/UpdateTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/up/UpdateTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,9 +6,9 @@ import org.basex.core.*; import org.basex.core.cmd.*; -import org.basex.data.atomic.*; import org.basex.io.*; import org.basex.query.*; +import org.basex.query.up.atomic.*; import org.basex.query.up.primitives.node.*; import org.junit.*; import org.junit.Test; diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/ArrayBuilderTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/ArrayBuilderTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/ArrayBuilderTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/ArrayBuilderTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -25,7 +25,7 @@ final Array arr = builder.freeze(); arr.checkInvariants(); assertEquals(len, arr.arraySize()); - final Iterator iter = arr.members(); + final Iterator iter = arr.iterator(0); for(int i = 0; i < len; i++) { assertTrue(iter.hasNext()); assertEquals(i, ((Int) iter.next()).itr()); @@ -43,7 +43,7 @@ final Array arr = builder.freeze(); arr.checkInvariants(); assertEquals(len, arr.arraySize()); - final Iterator iter = arr.members(); + final Iterator iter = arr.iterator(0); for(int i = 0; i < len; i++) { assertTrue(iter.hasNext()); assertEquals(i, ((Int) iter.next()).itr()); @@ -77,7 +77,7 @@ final Array arr = builder.freeze(); arr.checkInvariants(); assertEquals(len, arr.arraySize()); - final Iterator iter = arr.members(); + final Iterator iter = arr.iterator(0); for(int i = 0; i < len; i++) { assertTrue(iter.hasNext()); assertEquals(i, ((Int) iter.next()).itr()); @@ -113,7 +113,7 @@ arr.checkInvariants(); assertEquals(len, arr.arraySize()); final Iterator iter1 = deque.iterator(); - final Iterator iter2 = arr.members(); + final Iterator iter2 = arr.iterator(0); while(iter1.hasNext()) { assertTrue(iter2.hasNext()); assertEquals(iter1.next().intValue(), ((Int) iter2.next()).itr()); diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/ArrayConcatTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/ArrayConcatTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/ArrayConcatTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/ArrayConcatTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -17,9 +17,10 @@ public final class ArrayConcatTest { /** Generates and concatenates random arrays of a given size. */ @Test public void fuzzyTest() { - final Random rng = new Random(42); - for(int n = 0; n < 1000; n++) { + final Random rng = new Random(); + for(int n = 0; n < 1_000; n++) { for(int k = 0; k < 10; k++) { + rng.setSeed(10 * n + k); final int l = rng.nextInt(n + 1), r = n - l; Array a1 = Array.empty(), b1 = Array.empty(); @@ -42,7 +43,7 @@ assertEquals(n, a1.arraySize()); assertEquals(n, a2.size()); - final Iterator it1 = a1.members(); + final Iterator it1 = a1.iterator(0); final Iterator it2 = a2.iterator(); while(it1.hasNext()) { assertTrue(it2.hasNext()); diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/ArrayMembersTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/ArrayMembersTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/ArrayMembersTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/ArrayMembersTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,7 +9,7 @@ import org.junit.*; /** - * Tests for {@link Array#members(boolean)}. + * Tests for {@link Array#iterator(long)}. * * @author BaseX Team 2005-15, BSD License * @author Leo Woerteler @@ -17,8 +17,8 @@ public final class ArrayMembersTest { /** Random movements inside the array. */ @Test public void randomTest() { - final Random rng = new Random(1337); for(int n = 0; n < 1_000; n++) { + final Random rng = new Random(1337 + n); Array arr = Array.empty(); final ArrayList list = new ArrayList<>(n); for(int i = 0; i < n; i++) { @@ -27,9 +27,10 @@ list.add(insPos, i); } - final ListIterator it1 = arr.members(); - final ListIterator it2 = list.listIterator(); - int pos = 0; + final int startPos = rng.nextInt(n + 1); + final ListIterator it1 = arr.iterator(startPos); + final ListIterator it2 = list.listIterator(startPos); + int pos = startPos; for(int i = 0; i < 100; i++) { final int k = rng.nextInt(n + 1); if(rng.nextBoolean()) { diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/ArrayRemoveTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/ArrayRemoveTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/ArrayRemoveTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/ArrayRemoveTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -66,7 +66,7 @@ for(int k = 0; k < n; k++) { for(int i = 0; i < k; i++) { final Array arr2 = arr.remove(i); - final Iterator iter = arr2.members(); + final Iterator iter = arr2.iterator(0); for(int j = 0; j < k - 1; j++) { assertTrue(iter.hasNext()); assertEquals(j < i ? j : j + 1, ((Int) iter.next()).itr()); @@ -240,7 +240,7 @@ final ArrayList list = new ArrayList<>(n); for(int i = 0; i < n; i++) list.add(Int.get(i)); - Array arr = Array.from(list); + Array arr = Array.from(list.toArray(new Value[list.size()])); final Random rng = new Random(42); for(int i = 0; i < n; i++) { @@ -279,7 +279,7 @@ * @throws AssertionError of the check fails */ private static void assertContains(final Array arr, final int... vals) { - final Iterator iter = arr.members(); + final Iterator iter = arr.iterator(0); for(final int v : vals) { assertTrue(iter.hasNext()); assertEquals(v, ((Int) iter.next()).itr()); diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/ArrayReverseTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/ArrayReverseTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/ArrayReverseTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/ArrayReverseTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -22,9 +22,9 @@ Array arr = Array.empty(); for(int i = 0; i < n; i++) arr = arr.insertBefore(rng.nextInt(i + 1), Int.get(i)); assertEquals(n, arr.arraySize()); - final Array rev = arr.reverse(); - final ListIterator af = arr.members(false), ab = arr.members(true); - final ListIterator rf = rev.members(false), rb = rev.members(true); + final Array rev = arr.reverseArray(); + final ListIterator af = arr.iterator(0), ab = arr.iterator(n); + final ListIterator rf = rev.iterator(0), rb = rev.iterator(n); for(int i = 0; i < n; i++) { assertTrue(af.hasNext()); assertTrue(ab.hasPrevious()); diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/ArraySliceTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/ArraySliceTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/ArraySliceTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/ArraySliceTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -26,7 +26,7 @@ final Array sub = arr.subArray(pos, k); assertEquals(k, sub.arraySize()); sub.checkInvariants(); - final Iterator iter = sub.members(); + final Iterator iter = sub.iterator(0); for(int i = 0; i < k; i++) { final long res = ((Int) iter.next()).itr(); if(res != pos + i) { diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/VariousArrayTest.java basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/VariousArrayTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/query/value/array/VariousArrayTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/query/value/array/VariousArrayTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -148,7 +148,7 @@ final Array seq2 = seq.remove(i); assertEquals(k - 1, seq2.arraySize()); - final Iterator iter = seq2.members(); + final Iterator iter = seq2.iterator(0); for(int j = 0; j < k - 1; j++) { assertTrue(iter.hasNext()); assertEquals(j < i ? j : j + 1, ((Int) iter.next()).itr()); @@ -166,13 +166,13 @@ public void iteratorTest() { final int n = 1_000; Array seq = Array.empty(); - assertFalse(seq.members().hasNext()); + assertFalse(seq.iterator(0).hasNext()); for(int i = 0; i < n; i++) { final Int val = Int.get(i); seq = seq.cons(val).snoc(val); final int k = 2 * (i + 1); - final Iterator iter = seq.members(); + final Iterator iter = seq.iterator(0); for(int j = 0; j < k; j++) { assertTrue(iter.hasNext()); final Value next = iter.next(); diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/SandboxTest.java basex-8.2.3/basex-core/src/test/java/org/basex/SandboxTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/SandboxTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/SandboxTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,19 +1,6 @@ package org.basex; -import static org.basex.core.Text.*; -import static org.junit.Assert.*; - -import java.io.*; -import java.util.concurrent.*; - -import org.basex.api.client.*; import org.basex.core.*; -import org.basex.core.users.*; -import org.basex.io.*; -import org.basex.io.out.*; -import org.basex.util.*; -import org.basex.util.list.*; -import org.basex.util.options.*; import org.junit.*; /** @@ -22,166 +9,20 @@ * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ -public abstract class SandboxTest { - /** Database port. */ - protected static final int DB_PORT = 9996; - /** Event port. */ - protected static final int EVENT_PORT = 9997; - - /** Default output stream. */ - public static final PrintStream OUT = System.out; - /** Default error stream. */ - public static final PrintStream ERR = System.err; - /** Null output stream. */ - public static final PrintStream NULL = new PrintStream(new NullOutput()); - /** Test name. */ - protected static final String NAME = Util.className(SandboxTest.class); - /** Database context. */ - protected static Context context; - +public abstract class SandboxTest extends Sandbox { /** * Creates the sandbox. */ @BeforeClass - public static void initSandbox() { - final IOFile sb = sandbox(); - sb.delete(); - assertTrue("Sandbox could not be created.", sb.md()); - context = newContext(); + public static void initTests() { + initSandbox(); } /** * Removes test databases and closes the database context. */ @AfterClass - public static void closeContext() { - context.close(); - assertTrue("Sandbox could not be deleted.", sandbox().delete()); - } - - /** - * Creates a new specified context. - * @return context - */ - public static Context newContext() { - final IOFile sb = sandbox(); - Options.setSystem(StaticOptions.DBPATH.name(), sb.path() + "/data"); - Options.setSystem(StaticOptions.WEBPATH.name(), sb.path() + "/webapp"); - Options.setSystem(StaticOptions.RESTXQPATH.name(), sb.path() + "/webapp"); - Options.setSystem(StaticOptions.REPOPATH.name(), sb.path() + "/repo"); - try { - return new Context(); - } finally { - Options.setSystem(StaticOptions.DBPATH.name(), ""); - Options.setSystem(StaticOptions.WEBPATH.name(), ""); - Options.setSystem(StaticOptions.RESTXQPATH.name(), ""); - Options.setSystem(StaticOptions.REPOPATH.name(), ""); - } - } - - /** - * Creates a new, sandboxed server instance. - * @param args additional arguments - * @return server instance - * @throws IOException I/O exception - */ - public static BaseXServer createServer(final String... args) throws IOException { - try { - System.setOut(NULL); - final StringList sl = new StringList("-z", "-p" + DB_PORT, "-e" + EVENT_PORT, "-q"); - for(final String arg : args) sl.add(arg); - final BaseXServer server = new BaseXServer(sl.finish()); - server.context.soptions.set(StaticOptions.DBPATH, sandbox().path()); - return server; - } finally { - System.setOut(OUT); - } - } - - /** - * Stops a server instance. - * @param server server - * @throws IOException I/O exception - */ - public static void stopServer(final BaseXServer server) throws IOException { - try { - System.setOut(NULL); - if(server != null) server.stop(); - } finally { - System.setOut(OUT); - } - } - - /** - * Creates a client instance. - * @param login optional login data - * @return client instance - * @throws IOException I/O exception - */ - public static ClientSession createClient(final String... login) throws IOException { - final String user = login.length > 0 ? login[0] : UserText.ADMIN; - final String pass = login.length > 1 ? login[1] : UserText.ADMIN; - return new ClientSession(S_LOCALHOST, DB_PORT, user, pass); - } - - /** - * Returns the sandbox database path. - * @return database path - */ - public static IOFile sandbox() { - return new IOFile(Prop.TMP, NAME); - } - - /** - * Normalizes newlines in a query result. - * @param result input string - * @return normalized string - */ - public static String normNL(final Object result) { - return result.toString().replaceAll("(\r?\n|\r) *", "\n"); - } - - /** Client. */ - public static final class Client extends Thread { - /** Start signal. */ - private final CountDownLatch startSignal; - /** Stop signal. */ - private final CountDownLatch stopSignal; - /** Client session. */ - private final ClientSession session; - /** Command string. */ - private final Command cmd; - /** Fail flag. */ - public String error; - - /** - * Client constructor. - * @param c command string to execute - * @param start start signal - * @param stop stop signal - * @throws IOException I/O exception while establishing the session - */ - public Client(final Command c, final CountDownLatch start, final CountDownLatch stop) - throws IOException { - - session = createClient(); - cmd = c; - startSignal = start; - stopSignal = stop; - start(); - } - - @Override - public void run() { - try { - if(startSignal != null) startSignal.await(); - session.execute(cmd); - session.close(); - } catch(final Throwable ex) { - error = "\n" + cmd + '\n' + ex; - } finally { - if(stopSignal != null) stopSignal.countDown(); - } - } + public static void finishTests() { + finishSandbox(); } } diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/server/AdminStressTest.java basex-8.2.3/basex-core/src/test/java/org/basex/server/AdminStressTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/server/AdminStressTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/server/AdminStressTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,9 +6,7 @@ import java.util.concurrent.*; import org.basex.*; -import org.basex.api.client.*; import org.basex.core.cmd.*; -import org.basex.util.*; import org.junit.*; import org.junit.Test; @@ -19,7 +17,7 @@ * @author Dimitar Popov */ public final class AdminStressTest extends SandboxTest { - /** Number of clients/events. */ + /** Number of clients. */ private static final int NUM = 100; /** Server reference. */ private static BaseXServer server; @@ -43,35 +41,7 @@ } /** - * Start simultaneously clients which create events and clients which list all events. - * @throws Exception exception - */ - @Test - public void createAndListEvents() throws Exception { - final CountDownLatch start = new CountDownLatch(1); - final CountDownLatch stop = new CountDownLatch(NUM); - - final Client[] c1 = new Client[NUM]; - final Client[] c2 = new Client[NUM]; - for(int i = 0; i < NUM; ++i) { - c1[i] = new Client(new CreateEvent(NAME + i), start, stop); - c2[i] = new Client(new ShowEvents(), start, stop); - } - start.countDown(); // start all clients - stop.await(); - - Performance.sleep(200); - try(final ClientSession cs = createClient()) { - for(int i = 0; i < NUM; ++i) cs.execute("drop event " + NAME + i); - } - - for(final Client c : c1) if(c.error != null) fail(c.error); - for(final Client c : c2) if(c.error != null) fail(c.error); - } - - /** - * Start simultaneously clients which create events and clients which list all - * events. + * Test simultaneous client sessions. * @throws Exception exception */ @Test diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/server/EventTest.java basex-8.2.3/basex-core/src/test/java/org/basex/server/EventTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/server/EventTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/server/EventTest.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,285 +0,0 @@ -package org.basex.server; - -import static org.basex.query.func.Function.*; -import static org.junit.Assert.*; -import java.io.*; -import java.util.*; -import java.util.concurrent.*; - -import org.basex.*; -import org.basex.api.client.*; -import org.basex.core.*; -import org.basex.util.*; -import org.junit.*; - -/** - * This class tests the event API. - * - * @author BaseX Team 2005-15, BSD License - * @author Roman Raedle - * @author Andreas Weiler - */ -public final class EventTest extends SandboxTest { - /** Return value of function db:event. */ - private static final String RETURN = "ABCDEFGHIJKLMNOP"; - /** Event count. */ - private static final int EVENT_COUNT = 10; - /** Client count. */ - private static final int CLIENTS = 10; - - /** Server reference. */ - private static BaseXServer server; - /** Admin session. */ - private ClientSession session; - /** Control client sessions. */ - private final ClientSession[] sessions = new ClientSession[CLIENTS]; - - /** - * Starts the server. - * @throws IOException I/O exception - */ - @BeforeClass - public static void start() throws IOException { - server = createServer(); - } - - /** - * Starts the sessions. - * @throws IOException I/O exception - */ - @Before - public void startSessions() throws IOException { - session = createClient(); - // drop event if not done yet - try { - session.execute("drop event " + NAME); - } catch(final IOException ignored) { } - - final int sl = sessions.length; - for(int i = 0; i < sl; i++) sessions[i] = createClient(); - } - - /** - * Stops the sessions. - * @throws IOException I/O exception - */ - @After - public void stopSessions() throws IOException { - for(final ClientSession cs : sessions) cs.close(); - session.close(); - } - - /** - * Stops the server. - * @throws IOException I/O exception - */ - @AfterClass - public static void stop() throws IOException { - stopServer(server); - } - - /** - * Creates and drops events. - * @throws IOException I/O exception - */ - @Test - public void createDrop() throws IOException { - final String[] events = new String[EVENT_COUNT]; - for(int i = 0; i < EVENT_COUNT; i++) events[i] = NAME + i; - - try { - // create event - for(final String e : events) session.execute("create event " + e); - - // query must return all events - final HashSet names = new HashSet<>(); - final String result = session.execute("show events"); - for(final String line : result.split("\\r?\\n|\\r")) - if(line.startsWith("- ")) names.add(line.substring(2)); - - for(final String ev : events) - assertTrue("Event '" + ev + "' not created!", names.contains(ev)); - } finally { - // drop events as last action, preventing leftovers - for(final String ev : events) session.execute("drop event " + ev); - } - } - - /** - * Watches and unwatches events. - * @throws IOException I/O exception - */ - @Test - public void watchUnwatch() throws IOException { - // create event - session.execute("create event " + NAME); - // create event - try { - session.execute("create event " + NAME); - fail("This was supposed to fail."); - } catch(final IOException ex) { /* expected. */ } - - // watch an event - for(final ClientSession cs : sessions) { - cs.watch(NAME, new EventNotifier() { - @Override - public void notify(final String data) { } - }); - } - // watch an unknown event - try { - for(final ClientSession cs : sessions) { - cs.watch(NAME + 1, new EventNotifier() { - @Override - public void notify(final String data) { } - }); - fail("This was supposed to fail."); - } - } catch(final IOException ex) { /* expected. */ } - - // unwatch event - for(final ClientSession cs : sessions) { - cs.unwatch(NAME); - } - // unwatch unknown event - try { - for(final ClientSession cs : sessions) { - cs.unwatch(NAME + 1); - fail("This was supposed to fail."); - } - } catch(final IOException ex) { /* expected. */ } - - // drop the event - session.execute("drop event " + NAME); - // drop event - try { - session.execute("drop event " + NAME); - fail("This was supposed to fail."); - } catch(final IOException ex) { /* expected. */ } - } - - /** - * Runs event test with specified second query and without. - * @throws IOException I/O exception - * @throws InterruptedException waiting interrupted - */ - @Test - public void event() throws IOException, InterruptedException { - // create the event - // ignore the error that the event may already exist - try { - session.execute("create event " + NAME); - } catch(final BaseXException ignore) { } - - final CountDownLatch doneSignal = new CountDownLatch(sessions.length); - // watch event - for(final ClientSession cs : sessions) { - cs.watch(NAME, new EventNotifier() { - @Override - public void notify(final String data) { - doneSignal.countDown(); - assertEquals(RETURN, data); - } - }); - } - // fire an event - session.query(_DB_EVENT.args(NAME, RETURN)).execute(); - - // wait for half a second that the event is fired - assertTrue(doneSignal.await(500, TimeUnit.MILLISECONDS)); - - // all clients unwatch the events - for(final ClientSession cs : sessions) cs.unwatch(NAME); - - // drop event - session.execute("drop event " + NAME); - } - - /** - * Concurrent events. - * @throws Exception exception - */ - @Test - public void concurrent() throws Exception { - // create events - // ignore the error that the event may already exist - try { - session.execute("create event " + NAME); - session.execute("create event " + NAME + 1); - } catch(final BaseXException ignore) { } - - final CountDownLatch doneSignal = new CountDownLatch(CLIENTS * sessions.length); - // watch events on all clients - for(final ClientSession cs : sessions) { - cs.watch(NAME, new EventNotifier() { - @Override - public void notify(final String data) { - doneSignal.countDown(); - assertEquals(RETURN, data); - } - }); - cs.watch(NAME + 1, new EventNotifier() { - @Override - public void notify(final String data) { - doneSignal.countDown(); - assertEquals(RETURN, data); - } - }); - } - - // fire events - final Client[] clients = new Client[CLIENTS]; - for(int c = 0; c < CLIENTS; c++) { - clients[c] = new Client(c % 2 == 0, RETURN); - } - for(final Client c : clients) c.start(); - for(final Client c : clients) c.join(); - - // wait for half a second that the event is fired - assertTrue(doneSignal.await(500, TimeUnit.MILLISECONDS)); - - // unwatch events - for(final ClientSession cs : sessions) { - cs.unwatch(NAME); - cs.unwatch(NAME + 1); - } - - // drop event - session.execute("drop event " + NAME); - session.execute("drop event " + NAME + 1); - } - - /** Single client. */ - static final class Client extends Thread { - /** Client session. */ - private final ClientSession cs; - /** First event. */ - private final boolean first; - /** Event value. */ - private final String value; - - /** - * Default constructor. - * @param f first flag - * @param v value - * @throws IOException I/O exception - */ - Client(final boolean f, final String v) throws IOException { - cs = createClient(); - first = f; - value = v; - } - - @Override - public void run() { - try { - String name = NAME; - if(!first) name += 1; - cs.query(_DB_EVENT.args(name, value)).execute(); - cs.close(); - } catch(final Exception ex) { - Util.stack(ex); - } - } - } -} diff -Nru basex-8.1.1/basex-core/src/test/java/org/basex/server/SessionTest.java basex-8.2.3/basex-core/src/test/java/org/basex/server/SessionTest.java --- basex-8.1.1/basex-core/src/test/java/org/basex/server/SessionTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/java/org/basex/server/SessionTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -14,6 +14,7 @@ import org.basex.io.in.*; import org.basex.io.out.*; import org.basex.io.serial.*; +import org.basex.query.util.list.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; import org.basex.query.value.seq.*; @@ -442,7 +443,7 @@ } try(Query query = session.query("declare variable $a external; $a")) { - query.bind("a", Seq.get(new Item[] { Int.get(1), Str.get("X") })); + query.bind("a", new ItemList().add(Int.get(1)).add(Str.get("X")).value()); assertEqual("1", query.next()); assertEqual("X", query.next()); } diff -Nru basex-8.1.1/basex-core/src/test/resources/hello.xqm basex-8.2.3/basex-core/src/test/resources/hello.xqm --- basex-8.1.1/basex-core/src/test/resources/hello.xqm 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-core/src/test/resources/hello.xqm 2015-07-14 10:54:40.000000000 +0000 @@ -28,6 +28,6 @@ }; (:~ Private function returning a simple string. :) -declare %private function hello:internal() as xs:string { +declare %private %Q{ns}ignored function hello:internal() as xs:string { "hello world" }; diff -Nru basex-8.1.1/basex-examples/pom.xml basex-8.2.3/basex-examples/pom.xml --- basex-8.1.1/basex-examples/pom.xml 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/pom.xml 2015-07-14 10:54:40.000000000 +0000 @@ -8,7 +8,7 @@ org.basex basex-parent - 8.1.1 + 8.2.3 .. diff -Nru basex-8.1.1/basex-examples/.settings/checkstyle.xml basex-8.2.3/basex-examples/.settings/checkstyle.xml --- basex-8.1.1/basex-examples/.settings/checkstyle.xml 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/.settings/checkstyle.xml 2015-07-14 10:54:40.000000000 +0000 @@ -26,7 +26,7 @@ - + @@ -50,7 +50,6 @@ - diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/api/BaseXClient.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/api/BaseXClient.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/api/BaseXClient.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/api/BaseXClient.java 2015-07-14 10:54:40.000000000 +0000 @@ -17,9 +17,6 @@ public final class BaseXClient { /** UTF-8 charset. */ private static final Charset UTF8 = Charset.forName("UTF-8"); - /** Event notifications. */ - private final Map notifiers = - Collections.synchronizedMap(new HashMap()); /** Output stream. */ private final OutputStream out; /** Input stream (buffered). */ @@ -29,10 +26,6 @@ private final Socket socket; /** Command info. */ private String info; - /** Socket event reference. */ - private Socket esocket; - /** Socket event host. */ - private final String ehost; /** * Constructor. @@ -49,7 +42,6 @@ socket.connect(new InetSocketAddress(host, port), 5000); in = new BufferedInputStream(socket.getInputStream()); out = socket.getOutputStream(); - ehost = host; // receive server response final String[] response = receive().split(":"); @@ -148,46 +140,6 @@ } /** - * Watches an event. - * @param name event name - * @param notifier event notification - * @throws IOException I/O exception - */ - public void watch(final String name, final EventNotifier notifier) throws IOException { - out.write(10); - if(esocket == null) { - final int eport = Integer.parseInt(receive()); - // initialize event socket - esocket = new Socket(); - esocket.connect(new InetSocketAddress(ehost, eport), 5000); - final OutputStream os = esocket.getOutputStream(); - receive(in, os); - os.write(0); - os.flush(); - final InputStream is = esocket.getInputStream(); - is.read(); - listen(is); - } - send(name); - info = receive(); - if(!ok()) throw new IOException(info); - notifiers.put(name, notifier); - } - - /** - * Unwatches an event. - * @param name event name - * @throws IOException I/O exception - */ - public void unwatch(final String name) throws IOException { - out.write(11); - send(name); - info = receive(); - if(!ok()) throw new IOException(info); - notifiers.remove(name); - } - - /** * Returns command information. * @return string info */ @@ -202,7 +154,6 @@ public void close() throws IOException { send("exit"); out.flush(); - if(esocket != null) esocket.close(); socket.close(); } @@ -264,32 +215,6 @@ } /** - * Starts the listener thread. - * @param input input stream - */ - private void listen(final InputStream input) { - final BufferedInputStream bis = new BufferedInputStream(input); - new Thread() { - @Override - public void run() { - try { - while(true) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - receive(bis, baos); - final String name = new String(baos.toByteArray(), UTF8); - baos = new ByteArrayOutputStream(); - receive(bis, baos); - final String data = new String(baos.toByteArray(), UTF8); - notifiers.get(name).notify(data); - } - } catch(final IOException ex) { - // loop will be quit if no data can be received anymore - } - } - }.start(); - } - - /** * Sends an input stream to the server. * @param input xml input * @throws IOException I/O exception @@ -475,15 +400,4 @@ return s; } } - - /** - * Interface for event notifications. - */ - public interface EventNotifier { - /** - * Invoked when a database event was fired. - * @param value event string - */ - void notify(final String value); - } } \ No newline at end of file diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/api/EventExample.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/api/EventExample.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/api/EventExample.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/api/EventExample.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,49 +0,0 @@ -package org.basex.examples.api; - -import java.io.*; - -/** - * This example demonstrates how to trigger and receive database events. - * - * This example requires a running database server instance. - * Documentation: http://docs.basex.org/wiki/Clients - * - * @author BaseX Team 2005-15, BSD License - */ -public final class EventExample { - /** - * Main method. - * @param args command-line arguments - */ - public static void main(final String[] args) { - try { - final BaseXClient session1 = new BaseXClient("localhost", 1984, "admin", "admin"); - final BaseXClient session2 = new BaseXClient("localhost", 1984, "admin", "admin"); - - session1.execute("create event messenger"); - session2.watch("messenger", new Notifier()); - session2.query("for $i in 1 to 1000000 where $i = 0 return $i").execute(); - session1.query("db:event('messenger', 'Hello World!')").execute(); - session2.unwatch("messenger"); - session1.execute("drop event messenger"); - session1.close(); - session2.close(); - - } catch(final IOException e) { - e.printStackTrace(); - } - } - - /** - * Implementation of the event notifier interface. - */ - private static class Notifier implements BaseXClient.EventNotifier { - /** Constructor. */ - Notifier() { } - - @Override - public void notify(final String value) { - System.out.println("Message received: " + value); - } - } -} diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/local/BindContext.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/local/BindContext.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/local/BindContext.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/local/BindContext.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,8 +1,8 @@ package org.basex.examples.local; import org.basex.core.*; -import org.basex.data.*; import org.basex.query.*; +import org.basex.query.value.*; /** * This example demonstrates how items can be bound as context item of @@ -39,7 +39,7 @@ qp.context(item); // Execute the query - Result result = qp.execute(); + Value result = qp.value(); // Print result as string System.out.println("\n* Result:"); diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/local/BindVariables.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/local/BindVariables.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/local/BindVariables.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/local/BindVariables.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,8 +1,8 @@ package org.basex.examples.local; import org.basex.core.*; -import org.basex.data.*; import org.basex.query.*; +import org.basex.query.value.*; /** * This example demonstrates how items can be bound to variables with @@ -44,7 +44,7 @@ proc.bind("var2", number, "xs:integer"); // Execute the query - Result result = proc.execute(); + Value result = proc.value(); System.out.println("\n* Result:"); diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/local/RunQueries.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/local/RunQueries.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/local/RunQueries.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/local/RunQueries.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,10 +4,10 @@ import org.basex.core.*; import org.basex.core.cmd.*; -import org.basex.data.*; import org.basex.io.serial.*; import org.basex.query.*; import org.basex.query.iter.*; +import org.basex.query.value.*; import org.basex.query.value.item.*; /** @@ -82,7 +82,7 @@ // Create a query processor try(QueryProcessor proc = new QueryProcessor(query, context)) { // Execute the query - Result result = proc.execute(); + Value result = proc.value(); // Print result as string. System.out.println(result); diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/module/FruitsExample.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/module/FruitsExample.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/module/FruitsExample.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/module/FruitsExample.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,8 +1,8 @@ package org.basex.examples.module; import org.basex.core.*; -import org.basex.data.*; import org.basex.query.*; +import org.basex.query.value.*; /** * This example demonstrates how Java classes can be imported as XQuery modules. @@ -42,7 +42,7 @@ // Create a query processor try(QueryProcessor processor = new QueryProcessor(query, context)) { // Execute the query - Result result = processor.execute(); + Value result = processor.value(); System.out.println("\n* Result:"); diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/module/ModuleDemo.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/module/ModuleDemo.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/module/ModuleDemo.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/module/ModuleDemo.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,6 @@ package org.basex.examples.module; import org.basex.query.*; -import org.basex.query.iter.*; import org.basex.query.value.*; import org.basex.query.value.item.*; import org.basex.query.value.node.*; @@ -80,8 +79,8 @@ ValueBuilder vb = new ValueBuilder(); for(final Item item : value) { if(item instanceof DBNode) { - DBNode node = (DBNode) item; - int id = node.data.id(node.pre); + final DBNode node = (DBNode) item; + int id = node.data().id(node.pre()); vb.add(Int.get(id)); } } diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/server/ServerEventsGUI.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/server/ServerEventsGUI.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/server/ServerEventsGUI.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/server/ServerEventsGUI.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,163 +0,0 @@ -package org.basex.examples.server; - -import static org.basex.core.users.UserText.*; -import java.awt.*; -import java.awt.event.*; -import java.io.*; - -import javax.swing.*; -import javax.swing.border.*; - -import org.basex.*; -import org.basex.api.client.*; -import org.basex.server.*; -import org.basex.util.*; - -/** - * This class tests the event mechanism with a gui. - * - * @author BaseX Team 2005-15, BSD License - */ -public final class ServerEventsGUI extends JFrame { - /** Name of test database and user. */ - static final String NAME = Util.className(ServerEventsGUI.class); - /** Number of clients. */ - static final int CLIENTS = 2; - /** Color. */ - static final String RED = "RED"; - /** Color. */ - static final String BLUE = "BLUE"; - /** Color. */ - static final String YELLOW = "YELLOW"; - - /** Database server. */ - private static BaseXServer server; - /** Number of open windows. */ - private static int open = CLIENTS; - - /** Client session. */ - ClientSession session; - /** Main panel. */ - JPanel main; - - /** - * Main method, launching the test gui. - * @param args ignored - * @throws Exception exception - */ - public static void main(final String[] args) throws Exception { - server = new BaseXServer("-z"); - - // initialization - try(ClientSession cs = new ClientSession(server.context, ADMIN, ADMIN)) { - cs.execute("create event " + NAME); - cs.execute("create db " + NAME + " "); - } - - for(int i = 0; i < CLIENTS; i++) new ServerEventsGUI(i); - } - - /** - * Default Constructor. - * @param count window counter - * @throws IOException I/O exception - */ - private ServerEventsGUI(final int count) throws IOException { - super("Window " + (count + 1)); - - JPanel buttons = new JPanel(); - buttons.setLayout(new FlowLayout()); - buttons.setOpaque(false); - - final JTextArea area = new JTextArea(); - area.setPreferredSize(new Dimension(280, 90)); - area.setEditable(false); - area.setBorder(new CompoundBorder(new EtchedBorder(), - new EmptyBorder(10, 10, 10, 10))); - - // create notifier instance - final EventNotifier en = new EventNotifier() { - @Override - public void notify(final String value) { - // use event feedback to repaint background - Color c = Color.WHITE; - String tmp = value.replaceAll("\"", ""); - if(tmp.equals(RED)) { - c = Color.RED; - } else if(tmp.equals(BLUE)) { - c = Color.BLUE; - } else if(tmp.equals(YELLOW)) { - c = Color.YELLOW; - } - main.setBackground(c); - - // display updated XML fragment - try(final ClientQuery cq = session.query("/")) { - area.setText(cq.execute()); - } catch(IOException ex) { - ex.printStackTrace(); - } - } - }; - - // create session, open database and register event watcher - session = new ClientSession(server.context, ADMIN, ADMIN); - session.execute("open " + NAME); - session.watch(NAME, en); - - // create notification buttons - for(final String color : new String[] { RED, BLUE, YELLOW }) { - final JButton b = new JButton(color); - // create action event - b.addActionListener(new ActionListener() { - @Override - public void actionPerformed(final ActionEvent e) { - try { - // send update query and event - String query = - "let $color := '" + color + "' return " + - "(replace value of node /Application/Background with $color," + - " db:event('" + NAME + "', $color))"; - try(ClientQuery cq = session.query(query)) { - cq.execute(); - } - } catch(IOException ex) { - ex.printStackTrace(); - } - en.notify(b.getText()); - } - }); - buttons.add(b); - } - - main = new JPanel(); - main.setLayout(new BorderLayout()); - main.setBackground(Color.WHITE); - main.add(area, BorderLayout.CENTER); - main.add(buttons, BorderLayout.SOUTH); - - add(main); - pack(); - setLocation(300 * count, 0); - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - setVisible(true); - } - - @Override - public void dispose() { - try { - session.close(); - if(--open == 0) { - // no sessions left: drop event and database and stop server - try(ClientSession css = new ClientSession(server.context, ADMIN, ADMIN)) { - css.execute("drop event " + NAME); - css.execute("drop db " + NAME); - } - server.stop(); - } - super.dispose(); - } catch(Exception ex) { - ex.printStackTrace(); - } - } -} diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Main.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Main.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Main.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Main.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,10 +5,9 @@ import net.xqj.basex.*; /** - * XQJ Examples, derived from the XQJ Tutorial - * - * http://www.xquery.com/tutorials/xqj_tutorial - * from Marc van Cappellen. + * XQJ Examples, derived from an + * + * XQJ online tutorial. * * @author BaseX Team 2005-15, BSD License */ diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part10.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part10.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part10.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part10.java 2015-07-14 10:54:40.000000000 +0000 @@ -12,10 +12,9 @@ import org.xml.sax.helpers.*; /** - * XQJ Example, derived from the XQJ Tutorial - * - * http://www.xquery.com/tutorials/xqj_tutorial - * from Marc van Cappellen. + * XQJ Examples, derived from an + * + * XQJ online tutorial. * * Part 10: XML Pipelines. * diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part11.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part11.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part11.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part11.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,10 +7,9 @@ import javax.xml.xquery.*; /** - * XQJ Example, derived from the XQJ Tutorial - * - * http://www.xquery.com/tutorials/xqj_tutorial - * from Marc van Cappellen. + * XQJ Examples, derived from an + * + * XQJ online tutorial. * * Part 11: Processing Large Inputs. * diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part1.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part1.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part1.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part1.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,10 +5,9 @@ import net.xqj.basex.*; /** - * XQJ Example, derived from the XQJ Tutorial - * - * http://www.xquery.com/tutorials/xqj_tutorial - * from Marc van Cappellen. + * XQJ Examples, derived from an + * + * XQJ online tutorial. * * Part 1: An XQJ Introduction. * diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part2.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part2.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part2.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part2.java 2015-07-14 10:54:40.000000000 +0000 @@ -5,10 +5,9 @@ import javax.xml.xquery.*; /** - * XQJ Example, derived from the XQJ Tutorial - * - * http://www.xquery.com/tutorials/xqj_tutorial - * from Marc van Cappellen. + * XQJ Examples, derived from an + * + * XQJ online tutorial. * * Part 2: Configuring XQJ Connections. * diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part3.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part3.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part3.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part3.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,10 +9,9 @@ import org.w3c.dom.*; /** - * XQJ Example, derived from the XQJ Tutorial - * - * http://www.xquery.com/tutorials/xqj_tutorial - * from Marc van Cappellen. + * XQJ Examples, derived from an + * + * XQJ online tutorial. * * Part 3: Querying Data from XML Files or Java XML APIs. * diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part4.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part4.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part4.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part4.java 2015-07-14 10:54:40.000000000 +0000 @@ -11,10 +11,9 @@ import org.xml.sax.helpers.*; /** - * XQJ Example, derived from the XQJ Tutorial - * - * http://www.xquery.com/tutorials/xqj_tutorial - * from Marc van Cappellen. + * XQJ Examples, derived from an + * + * XQJ online tutorial. * * Part 4: Processing Results. * diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part5.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part5.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part5.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part5.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,10 +6,9 @@ import javax.xml.xquery.*; /** - * XQJ Example, derived from the XQJ Tutorial - * - * http://www.xquery.com/tutorials/xqj_tutorial - * from Marc van Cappellen. + * XQJ Examples, derived from an + * + * XQJ online tutorial. * * Part 5: Serializing Results. * diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part6.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part6.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part6.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part6.java 2015-07-14 10:54:40.000000000 +0000 @@ -3,10 +3,9 @@ import javax.xml.xquery.*; /** - * XQJ Example, derived from the XQJ Tutorial - * - * http://www.xquery.com/tutorials/xqj_tutorial - * from Marc van Cappellen. + * XQJ Examples, derived from an + * + * XQJ online tutorial. * * Part 6: Manipulating Static Context. * diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part7.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part7.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part7.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part7.java 2015-07-14 10:54:40.000000000 +0000 @@ -4,10 +4,9 @@ import javax.xml.xquery.*; /** - * XQJ Example, derived from the XQJ Tutorial - * - * http://www.xquery.com/tutorials/xqj_tutorial - * from Marc van Cappellen. + * XQJ Examples, derived from an + * + * XQJ online tutorial. * * Part 7: XQuery Type System. * diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part8.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part8.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part8.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part8.java 2015-07-14 10:54:40.000000000 +0000 @@ -10,10 +10,9 @@ import org.w3c.dom.*; /** - * XQJ Example, derived from the XQJ Tutorial - * - * http://www.xquery.com/tutorials/xqj_tutorial - * from Marc van Cappellen. + * XQJ Examples, derived from an + * + * XQJ online tutorial. * * Part 8: Binding External Variables. * diff -Nru basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part9.java basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part9.java --- basex-8.1.1/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part9.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-examples/src/main/java/org/basex/examples/xqj/tutorial/Part9.java 2015-07-14 10:54:40.000000000 +0000 @@ -7,10 +7,9 @@ import javax.xml.xquery.*; /** - * XQJ Example, derived from the XQJ Tutorial - * - * http://www.xquery.com/tutorials/xqj_tutorial - * from Marc van Cappellen. + * XQJ Examples, derived from an + * + * XQJ online tutorial. * * Part 9: Creating XDM Instances. * diff -Nru basex-8.1.1/basex-tests/pom.xml basex-8.2.3/basex-tests/pom.xml --- basex-8.1.1/basex-tests/pom.xml 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-tests/pom.xml 2015-07-14 10:54:40.000000000 +0000 @@ -8,7 +8,7 @@ org.basex basex-parent - 8.1.1 + 8.2.3 .. diff -Nru basex-8.1.1/basex-tests/.settings/checkstyle.xml basex-8.2.3/basex-tests/.settings/checkstyle.xml --- basex-8.1.1/basex-tests/.settings/checkstyle.xml 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-tests/.settings/checkstyle.xml 2015-07-14 10:54:40.000000000 +0000 @@ -1,12 +1,12 @@ - - @@ -26,7 +26,7 @@ - + @@ -50,7 +50,6 @@ - diff -Nru basex-8.1.1/basex-tests/src/main/java/org/basex/tests/bxapi/xdm/XdmValue.java basex-8.2.3/basex-tests/src/main/java/org/basex/tests/bxapi/xdm/XdmValue.java --- basex-8.1.1/basex-tests/src/main/java/org/basex/tests/bxapi/xdm/XdmValue.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-tests/src/main/java/org/basex/tests/bxapi/xdm/XdmValue.java 2015-07-14 10:54:40.000000000 +0000 @@ -9,7 +9,6 @@ import org.basex.query.value.seq.*; import org.basex.query.value.type.*; import org.basex.tests.bxapi.*; -import org.basex.util.*; /** * Wrapper for representing XQuery values. @@ -33,7 +32,7 @@ * @return node name */ public String getBaseURI() { - throw Util.notExpected("Item must be a node: " + internal()); + throw new XQueryException(new QueryException("Item must be a node: " + internal())); } /** @@ -41,7 +40,7 @@ * @return node name */ public QName getName() { - throw Util.notExpected("Item must be a node: " + internal()); + throw new XQueryException(new QueryException("Item must be a node: " + internal())); } /** @@ -49,7 +48,8 @@ * @return node name */ public boolean getBoolean() { - throw Util.notExpected("Value has no boolean representation: " + internal()); + throw new XQueryException(new QueryException( + "Value has no boolean representation: " + internal())); } /** @@ -60,7 +60,8 @@ try { return Long.parseLong(getString()); } catch(final NumberFormatException ex) { - throw Util.notExpected("Value has no integer representation: " + internal()); + throw new XQueryException(new QueryException( + "Value has no integer representation: " + internal())); } } @@ -69,7 +70,8 @@ * @return node name */ public String getString() { - throw Util.notExpected("Value has no string representation: " + internal()); + throw new XQueryException(new QueryException( + "Value has no string representation: " + internal())); } /** diff -Nru basex-8.1.1/basex-tests/src/main/java/org/basex/tests/bxapi/XQuery.java basex-8.2.3/basex-tests/src/main/java/org/basex/tests/bxapi/XQuery.java --- basex-8.1.1/basex-tests/src/main/java/org/basex/tests/bxapi/XQuery.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-tests/src/main/java/org/basex/tests/bxapi/XQuery.java 2015-07-14 10:54:40.000000000 +0000 @@ -6,7 +6,6 @@ import javax.xml.namespace.*; import org.basex.core.Context; -import org.basex.io.serial.*; import org.basex.query.*; import org.basex.query.expr.*; import org.basex.query.func.*; @@ -242,11 +241,11 @@ } /** - * Returns serialization properties. - * @return serialization properties + * Returns the query processor. + * @return query processor */ - public SerializerOptions serializer() { - return qp.qc.serParams(); + public QueryProcessor qp() { + return qp; } @Override diff -Nru basex-8.1.1/basex-tests/src/main/java/org/basex/tests/w3c/QT3TS.java basex-8.2.3/basex-tests/src/main/java/org/basex/tests/w3c/QT3TS.java --- basex-8.1.1/basex-tests/src/main/java/org/basex/tests/w3c/QT3TS.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-tests/src/main/java/org/basex/tests/w3c/QT3TS.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,7 +1,7 @@ package org.basex.tests.w3c; import static org.basex.tests.w3c.QT3Constants.*; -import static org.basex.util.Prop.NL; +import static org.basex.util.Prop.*; import static org.basex.util.Token.*; import java.io.*; @@ -313,16 +313,11 @@ } final QT3Result returned = new QT3Result(); + returned.env = env; + try { + environment(query, env); if(env != null) { - // bind namespaces - for(final HashMap ns : env.namespaces) { - query.namespace(ns.get(PREFIX), ns.get(URI)); - } - // bind variables - for(final HashMap par : env.params) { - query.bind(par.get(NNAME), new XQuery(par.get(SELECT), ctx).value()); - } // bind documents for(final HashMap src : env.sources) { // add document reference @@ -337,7 +332,8 @@ } // bind resources for(final HashMap src : env.resources) { - query.addResource(src.get(URI), file(base, src.get(FILE)), src.get(ENCODING)); + query.addResource(src.get(URI), file(base, src.get(FILE)), + src.get(QT3Constants.ENCODING)); } // bind collections query.addCollection(env.collURI, env.collSources.toArray()); @@ -359,7 +355,7 @@ // run query returned.value = query.value(); - returned.sprop = query.serializer(); + returned.query = query; } catch(final XQueryException ex) { returned.xqerror = ex; returned.value = null; @@ -388,8 +384,7 @@ } else if(returned.xqerror != null) { res = returned.xqerror.getCode() + ": " + returned.xqerror.getLocalizedMessage(); } else { - returned.sprop.set(SerializerOptions.OMIT_XML_DECLARATION, YesNo.YES); - res = serialize(returned.value, returned.sprop); + res = serialize(returned); } } catch(final XQueryException ex) { res = ex.getCode() + ": " + ex.getLocalizedMessage(); @@ -410,13 +405,33 @@ } /** + * Assigns the query environment. + * @param query query + * @param env environment + * @return query; + */ + private XQuery environment(XQuery query, QT3Env env) { + if(env != null) { + // bind namespaces + for(final HashMap ns : env.namespaces) { + query.namespace(ns.get(PREFIX), ns.get(URI)); + } + // bind variables + for(final HashMap par : env.params) { + query.bind(par.get(NNAME), new XQuery(par.get(SELECT), ctx).value()); + } + } + return query; + } + + /** * Normalizes special characters in the specified string. * @param in input string * @return result */ private String normSpecial(final String in) { - // return QueryProcessor.removeComments(in, maxout); - return in.replace("\n", "%0A").replace("\r", "%0D").replace("\t", "%09"); + return in.replaceAll("<\\?xml.*?>", "").replace("\n", "%0A").replace("\r", "%0D"). + replace("\t", "%09"); } /** Flags for dependencies that are not supported. */ @@ -462,49 +477,49 @@ /** * Tests the result of a test case. - * @param returned resulting value + * @param result query result * @param expected expected result * @return {@code null} if test was successful; otherwise, expected test suite result */ - private String test(final QT3Result returned, final XdmValue expected) { + private String test(final QT3Result result, final XdmValue expected) { final String type = expected.getName().getLocalPart(); - final XdmValue value = returned.value; - try { String msg; if(type.equals("error")) { - msg = assertError(returned, expected); + msg = assertError(result, expected); } else if(type.equals("assert-serialization-error")) { - msg = assertSerializationError(returned, expected, returned.sprop); + msg = assertSerializationError(result, expected); } else if(type.equals("all-of")) { - msg = allOf(returned, expected); + msg = allOf(result, expected); + } else if(type.equals("not")) { + msg = not(result, expected); } else if(type.equals("any-of")) { - msg = anyOf(returned, expected); - } else if(value != null) { + msg = anyOf(result, expected); + } else if(result.value != null) { if(type.equals("assert")) { - msg = assertQuery(value, expected); + msg = assertQuery(result, expected); } else if(type.equals("assert-count")) { - msg = assertCount(value, expected); + msg = assertCount(result, expected); } else if(type.equals("assert-deep-eq")) { - msg = assertDeepEq(value, expected); + msg = assertDeepEq(result, expected); } else if(type.equals("assert-empty")) { - msg = assertEmpty(value); + msg = assertEmpty(result); } else if(type.equals("assert-eq")) { - msg = assertEq(value, expected); + msg = assertEq(result, expected); } else if(type.equals("assert-false")) { - msg = assertBoolean(value, false); + msg = assertBoolean(result, false); } else if(type.equals("assert-permutation")) { - msg = assertPermutation(value, expected); + msg = assertPermutation(result, expected); } else if(type.equals("assert-xml")) { - msg = assertXML(value, expected); + msg = assertXML(result, expected); } else if(type.equals("serialization-matches")) { - msg = serializationMatches(value, expected, returned.sprop); + msg = serializationMatches(result, expected); } else if(type.equals("assert-string-value")) { - msg = assertStringValue(value, expected); + msg = assertStringValue(result, expected); } else if(type.equals("assert-true")) { - msg = assertBoolean(value, true); + msg = assertBoolean(result, true); } else if(type.equals("assert-type")) { - msg = assertType(value, expected); + msg = assertType(result, expected); } else { msg = "Test type not supported: " + type; } @@ -520,16 +535,16 @@ /** * Tests error. - * @param returned query result + * @param result query result * @param expected expected result * @return optional expected test suite result */ - private String assertError(final QT3Result returned, final XdmValue expected) { + private String assertError(final QT3Result result, final XdmValue expected) { final String exp = asString('@' + CODE, expected); - if(returned.xqerror == null) return exp; + if(result.xqerror == null) return exp; if(!errors || exp.equals("*")) return null; - final QNm resErr = returned.xqerror.getException().qname(); + final QNm resErr = result.xqerror.getException().qname(); String name = exp, uri = string(QueryText.ERROR_URI); final Matcher m = BIND.matcher(exp); @@ -542,15 +557,30 @@ } /** + * Tests not. + * @param result query result + * @param exp expected result + * @return optional expected test suite result + */ + private String not(final QT3Result result, final XdmValue exp) { + final TokenBuilder tb = new TokenBuilder(); + for(final XdmItem it : environment(new XQuery("*", ctx), result.env).context(exp)) { + final String msg = test(result, it); + if(msg != null) tb.add(tb.isEmpty() ? "" : ", ").add(msg); + } + return tb.isEmpty() ? "Error" : null; + } + + /** * Tests all-of. - * @param res resulting value + * @param result query result * @param exp expected result * @return optional expected test suite result */ - private String allOf(final QT3Result res, final XdmValue exp) { + private String allOf(final QT3Result result, final XdmValue exp) { final TokenBuilder tb = new TokenBuilder(); - for(final XdmItem it : new XQuery("*", ctx).context(exp)) { - final String msg = test(res, it); + for(final XdmItem it : environment(new XQuery("*", ctx), result.env).context(exp)) { + final String msg = test(result, it); if(msg != null) tb.add(tb.isEmpty() ? "" : ", ").add(msg); } return tb.isEmpty() ? null : tb.toString(); @@ -558,14 +588,14 @@ /** * Tests any-of. - * @param res resulting value + * @param result query result * @param exp expected result * @return optional expected test suite result */ - private String anyOf(final QT3Result res, final XdmValue exp) { + private String anyOf(final QT3Result result, final XdmValue exp) { final TokenBuilder tb = new TokenBuilder(); - for(final XdmItem it : new XQuery("*", ctx).context(exp)) { - final String msg = test(res, it); + for(final XdmItem it : environment(new XQuery("*", ctx), result.env).context(exp)) { + final String msg = test(result, it); if(msg == null) return null; tb.add(tb.isEmpty() ? "" : ", ").add(msg); } @@ -574,15 +604,16 @@ /** * Tests assertion. - * @param returned resulting value + * @param result query result * @param expected expected result * @return optional expected test suite result */ - private String assertQuery(final XdmValue returned, final XdmValue expected) { + private String assertQuery(final QT3Result result, final XdmValue expected) { final String exp = expected.getString(); try { final String qu = "declare variable $result external; " + exp; - return new XQuery(qu, ctx).bind("$result", returned).value().getBoolean() ? null : exp; + return environment(new XQuery(qu, ctx), result.env).bind("$result", result.value). + value().getBoolean() ? null : exp; } catch(final XQueryException ex) { // should not occur return ex.getException().getMessage(); @@ -591,27 +622,28 @@ /** * Tests count. - * @param returned resulting value + * @param result query result * @param expected expected result * @return optional expected test suite result */ - private static String assertCount(final XdmValue returned, final XdmValue expected) { + private static String assertCount(final QT3Result result, final XdmValue expected) { final long exp = expected.getInteger(); - final int res = returned.size(); + final int res = result.value.size(); return exp == res ? null : Util.info("% items (% found)", exp, res); } /** * Tests equality. - * @param returned resulting value + * @param result query result * @param expected expected result * @return optional expected test suite result */ - private String assertEq(final XdmValue returned, final XdmValue expected) { + private String assertEq(final QT3Result result, final XdmValue expected) { final String exp = expected.getString(); try { final String qu = "declare variable $returned external; $returned eq " + exp; - return new XQuery(qu, ctx).bind("$returned", returned).value().getBoolean() ? null : exp; + return environment(new XQuery(qu, ctx), result.env).bind("$returned", result.value). + value().getBoolean() ? null : exp; } catch(final XQueryException err) { // numeric overflow: try simple string comparison return err.getCode().equals("FOAR0002") && exp.equals(expected.getString()) @@ -621,29 +653,29 @@ /** * Tests deep equals. - * @param returned resulting value + * @param result query result * @param expected expected result * @return optional expected test suite result */ - private String assertDeepEq(final XdmValue returned, final XdmValue expected) { - final XdmValue exp = new XQuery(expected.getString(), ctx).value(); - return exp.deepEqual(returned) ? null : exp.toString(); + private String assertDeepEq(final QT3Result result, final XdmValue expected) { + final XdmValue exp = environment(new XQuery(expected.getString(), ctx), result.env).value(); + return exp.deepEqual(result.value) ? null : exp.toString(); } /** * Tests permutation. - * @param returned resulting value + * @param result query result * @param expected expected result * @return optional expected test suite result */ - private String assertPermutation(final XdmValue returned, final XdmValue expected) { + private String assertPermutation(final QT3Result result, final XdmValue expected) { // cache expected results final HashSet exp = new HashSet<>(); - for(final XdmItem it : new XQuery(expected.getString(), ctx)) + for(final XdmItem it : environment(new XQuery(expected.getString(), ctx), result.env)) exp.add(it.getString()); // cache actual results final HashSet res = new HashSet<>(); - for(final XdmItem it : returned) res.add(it.getString()); + for(final XdmItem it : result.value) res.add(it.getString()); if(exp.size() != res.size()) return Util.info("% results (found: %)", exp.size(), res.size()); @@ -660,11 +692,11 @@ /** * Tests the serialized result. - * @param returned resulting value + * @param result query result * @param expected expected result * @return optional expected test suite result */ - private String assertXML(final XdmValue returned, final XdmValue expected) { + private String assertXML(final QT3Result result, final XdmValue expected) { final String file = asString("@file", expected); final boolean norm = asBoolean("@normalize-space=('true','1')", expected); final boolean pref = asBoolean("@ignore-prefixes=('true','1')", expected); @@ -675,6 +707,7 @@ exp = normNL(exp); if(norm) exp = string(normalize(token(exp))); + final XdmValue returned = result.value; final String res = normNL( asString("serialize(., map{ 'indent':'no','method':'xml' })", returned)); if(exp.equals(res)) return null; @@ -695,54 +728,45 @@ /** * Tests the serialized result. - * @param returned resulting value + * @param result query result * @param expected expected result - * @param sprop serialization properties * @return optional expected test suite result */ - private String serializationMatches(final XdmValue returned, final XdmValue expected, - final SerializerOptions sprop) { - - final String exp = expected.getString(); - final String flags = asString("@flags", expected); - final int flgs = flags.contains("i") ? Pattern.CASE_INSENSITIVE : 0; - final Pattern pat = Pattern.compile("^.*" + exp + ".*", flgs | - Pattern.MULTILINE | Pattern.DOTALL); - - if(sprop.get(SerializerOptions.METHOD) == SerialMethod.ADAPTIVE) - sprop.set(SerializerOptions.OMIT_XML_DECLARATION, YesNo.YES); - + private String serializationMatches(final QT3Result result, final XdmValue expected) { try { - return pat.matcher("^.*" + serialize(returned, sprop) + ".*").matches() ? null : exp; - } catch(final IOException ex) { - return Util.info("% (found: %)", exp, ex); + final String flags = asString("@flags", expected); + final XdmValue ret = XdmValue.get(Str.get(serialize(result))); + final String qu = "declare variable $returned external;" + + "declare variable $expected external;" + + "matches($returned, string($expected), '" + flags + "')"; + + return environment(new XQuery(qu, ctx), result.env).bind("returned", ret). + bind("expected", expected).value().getBoolean() ? null : expected.getString(); + } catch(final Exception err) { + return Util.info("% (found: %)", expected.getString(), err); } } - /** * Tests a serialization error. - * @param returned result + * @param result returned result * @param expected expected result - * @param sprop serialization properties * @return optional expected test suite result */ - private String assertSerializationError(final QT3Result returned, final XdmValue expected, - final SerializerOptions sprop) { - + private String assertSerializationError(final QT3Result result, final XdmValue expected) { final String expCode = asString('@' + CODE, expected); - if(returned.xqerror != null) { + if(result.xqerror != null) { if(!errors || expCode.equals("*")) return null; - final String resCode = string(returned.xqerror.getException().qname().local()); + final String resCode = string(result.xqerror.getException().qname().local()); if(resCode.equals(expCode)) return null; } try { - if(returned.value != null) serialize(returned.value, sprop); + if(result.value != null) serialize(result); return expCode; - } catch(final QueryIOException ex) { + } catch(final QueryException ex) { if(!errors || expCode.equals("*")) return null; - final String resCode = string(ex.getCause().qname().local()); + final String resCode = string(ex.qname().local()); if(resCode.equals(expCode)) return null; return Util.info("% (found: %)", expCode, ex); } catch(final IOException ex) { @@ -752,28 +776,30 @@ /** * Serializes values. - * @param returned resulting value - * @param sprop serialization properties + * @param result query result * @return optional expected test suite result + * @throws QueryException query exception * @throws IOException I/O exception */ - private static String serialize(final XdmValue returned, final SerializerOptions sprop) - throws IOException { - - final ArrayOutput ao = new ArrayOutput(); - try(final Serializer ser = Serializer.get(ao, sprop)) { - for(final Item it : returned.internal()) ser.serialize(it); + private static String serialize(final QT3Result result) throws QueryException, IOException { + try { + final ArrayOutput ao = new ArrayOutput(); + try(final Serializer ser = result.query.qp().getSerializer(ao)) { + for(final Item it : result.value.internal()) ser.serialize(it); + } + return ao.toString(); + } catch(final QueryIOException ex) { + throw ex.getCause(); } - return ao.toString(); } /** * Tests string value. - * @param returned resulting value + * @param result query result * @param expected expected result * @return optional expected test suite result */ - private String assertStringValue(final XdmValue returned, final XdmValue expected) { + private String assertStringValue(final QT3Result result, final XdmValue expected) { String exp = expected.getString(); // normalize space final boolean norm = asBoolean("@normalize-space=('true','1')", expected); @@ -781,7 +807,7 @@ final TokenBuilder tb = new TokenBuilder(); int c = 0; - for(final XdmItem it : returned) { + for(final XdmItem it : result.value) { if(c++ != 0) tb.add(' '); tb.add(it.getString()); } @@ -792,35 +818,38 @@ /** * Tests boolean. - * @param returned resulting value + * @param result query result * @param expected expected * @return optional expected test suite result */ - private static String assertBoolean(final XdmValue returned, final boolean expected) { + private static String assertBoolean(final QT3Result result, final boolean expected) { + final XdmValue returned = result.value; return returned.getType().eq(SeqType.BLN) && returned.getBoolean() == expected ? null : Util.info(expected); } /** * Tests empty sequence. - * @param value resulting value + * @param result query result * @return optional expected test suite result */ - private static String assertEmpty(final XdmValue value) { - return value == XdmEmpty.EMPTY ? null : ""; + private static String assertEmpty(final QT3Result result) { + return result.value == XdmEmpty.EMPTY ? null : ""; } /** * Tests type. - * @param returned resulting value + * @param result query result * @param expected expected result * @return optional expected test suite result */ - private String assertType(final XdmValue returned, final XdmValue expected) { + private String assertType(final QT3Result result, final XdmValue expected) { final String exp = expected.getString(); try { final String qu = "declare variable $returned external; $returned instance of " + exp; - final XQuery query = new XQuery(qu, ctx); + final XQuery query = environment(new XQuery(qu, ctx), result.env); + + final XdmValue returned = result.value; return query.bind("returned", returned).value().getBoolean() ? null : Util.info("Type '%' (found: '%')", exp, returned.getType().toString()); } catch(final XQueryException ex) { @@ -953,8 +982,10 @@ * Structure for storing XQuery results. */ static class QT3Result { - /** Serialization parameters. */ - SerializerOptions sprop; + /** Test environment. */ + QT3Env env; + /** Query instance. */ + XQuery query; /** Query result. */ XdmValue value; /** Query exception. */ diff -Nru basex-8.1.1/basex-tests/src/test/java/org/basex/performance/UpdIndexRandomTest.java basex-8.2.3/basex-tests/src/test/java/org/basex/performance/UpdIndexRandomTest.java --- basex-8.1.1/basex-tests/src/test/java/org/basex/performance/UpdIndexRandomTest.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-tests/src/test/java/org/basex/performance/UpdIndexRandomTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,81 @@ +package org.basex.performance; + +import static org.basex.query.func.Function.*; +import static org.junit.Assert.*; + +import java.util.*; + +import org.basex.*; +import org.basex.core.*; +import org.basex.core.cmd.*; +import org.basex.core.cmd.Set; +import org.basex.util.*; +import org.basex.util.list.*; +import org.junit.*; +import org.junit.Test; + +/** + * This test class performs random incremental updates with random documents. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class UpdIndexRandomTest extends SandboxTest { + /** Number of different documents. */ + private static final int DOCS = 20; + /** Number of runs. */ + private static final int RUNS = 500; + + /** + * Initializes the test. + * @throws Exception exception + */ + @Before + public void init() throws Exception { + new Set(MainOptions.UPDINDEX, true).execute(context); + new Set(MainOptions.ATTRINDEX, false).execute(context); + new CreateDB(NAME).execute(context); + } + + /** + * Incremental test. + * @throws Exception exception + */ + @Test + public void insertInto() throws Exception { + final Random rnd = new Random(0); + + // create random words + int cap = 1000; + final StringList words = new StringList(cap); + for(int w = 0; w < cap; w++) { + final int r = 1 + rnd.nextInt(10); + final TokenBuilder tmp = new TokenBuilder(r); + for(int i = 0; i < r; i++) tmp.add('A' + rnd.nextInt(26)); + words.add(tmp.toString()); + } + + for(int r = 0; r < RUNS; r++) { + final String path = "doc" + rnd.nextInt(DOCS); + // create random document + final TokenBuilder doc = new TokenBuilder(""); + + final int offset = rnd.nextInt(cap - DOCS); + for(int i = 0; i < DOCS; i++) doc.add("").add(words.get(offset + i)).add(""); + doc.add(""); + new Replace(path, doc.toString()).execute(context); + + for(int d = 0; d < DOCS; d++) { + final String word = words.get(offset + d); + final String query = _DB_OPEN.args(NAME, path) + "//a[text() = '" + word + "']"; + final String expected = "" + word + ""; + final String result = new XQuery(query).execute(context); + if(!result.startsWith(expected)) { + fail(new TokenBuilder("\nExpected: " + expected + + "\nResult: " + result + "\nRun: " + r + "\nDoc: " + d + + "\nQuery: " + query + "\nDocument: " + doc).toString()); + } + } + } + } +} diff -Nru basex-8.1.1/basex-tests/src/test/java/org/basex/performance/UpdIndexTest.java basex-8.2.3/basex-tests/src/test/java/org/basex/performance/UpdIndexTest.java --- basex-8.1.1/basex-tests/src/test/java/org/basex/performance/UpdIndexTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-tests/src/test/java/org/basex/performance/UpdIndexTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -156,11 +156,11 @@ * Runs the specified query. * @param query query string * @return result - * @throws QueryException database exception + * @throws Exception exception */ - protected static String query(final String query) throws QueryException { + protected static String query(final String query) throws Exception { try(final QueryProcessor qp = new QueryProcessor(query, context)) { - return qp.execute().toString().replaceAll("(\\r|\\n) *", ""); + return normNL(qp.value().serialize().toString()); } } @@ -168,9 +168,9 @@ * Checks if a query yields the specified string. * @param query query to be run * @param result query result - * @throws QueryException database exception + * @throws Exception exception */ - protected static void query(final String query, final Object result) throws QueryException { + protected static void query(final String query, final Object result) throws Exception { assertEquals(result.toString(), query(query)); } } diff -Nru basex-8.1.1/basex-tests/src/test/java/org/basex/performance/XMarkTest.java basex-8.2.3/basex-tests/src/test/java/org/basex/performance/XMarkTest.java --- basex-8.1.1/basex-tests/src/test/java/org/basex/performance/XMarkTest.java 1970-01-01 00:00:00.000000000 +0000 +++ basex-8.2.3/basex-tests/src/test/java/org/basex/performance/XMarkTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -0,0 +1,208 @@ +package org.basex.performance; + +import static org.basex.core.Text.*; + +import java.io.*; +import java.math.*; + +import org.basex.*; +import org.basex.api.client.*; +import org.basex.core.*; +import org.basex.core.cmd.*; +import org.basex.core.users.*; +import org.basex.io.*; +import org.basex.util.*; +import org.basex.util.list.*; +import org.junit.*; +import org.junit.Test; + +/** + * Runs the XMark tests. + * + * @author BaseX Team 2005-15, BSD License + * @author Christian Gruen + */ +public final class XMarkTest { + /** Name of database. */ + private static final String DB = "111mb"; + + /** Test directory. */ + private static final IOFile DIR = new IOFile(Prop.TMP, "XMark"); + /** Output file. */ + private static final IOFile FILE = new IOFile(DIR, "master- " + DB + ".graph"); + + /** Maximum time per query (ms). */ + private static final int MAX = 2000; + + /** Queries. */ + private static final String[] QUERIES = { + "let $auction := . return for $b in $auction/site/people/person[@id = \"person0\"]" + + "return $b/name/text()", + "let $auction := . return for $b in $auction/site/open_auctions/open_auction " + + "return {$b/bidder[1]/increase/text()}", + "let $auction := . return for $b in $auction/site/open_auctions/open_auction " + + "where zero-or-one($b/bidder[1]/increase/text()) * 2 <= $b/bidder[last()]/increase/text() " + + "return ", + "let $auction := . return for $b in $auction/site/open_auctions/open_auction " + + "where some $pr1 in $b/bidder/personref[@person = \"person20\"], " + + "$pr2 in $b/bidder/personref[@person = \"person51\"] satisfies $pr1 << $pr2 " + + "return {$b/reserve/text()}", + "let $auction := . return count( for $i in $auction/site/closed_auctions/closed_auction " + + "where $i/price/text() >= 40 return $i/price )", + "let $auction := . return for $b in $auction//site/regions return count($b//item)", + "let $auction := . return for $p in $auction/site " + + "return count($p//description) + count($p//annotation) + count($p//emailaddress)", + "let $auction := . return for $p in $auction/site/people/person " + + "let $a := for $t in $auction/site/closed_auctions/closed_auction " + + "where $t/buyer/@person = $p/@id return $t " + + "return {count($a)}", + "let $auction := . return let $ca := $auction/site/closed_auctions/closed_auction " + + "return let $ei := $auction/site/regions/europe/item for $p in $auction/site/people/person " + + "let $a := for $t in $ca where $p/@id = $t/buyer/@person return " + + "let $n := for $t2 in $ei where $t/itemref/@item = $t2/@id return $t2 " + + "return {$n/name/text()} return {$a}", + "let $auction := . return for $i in " + + "distinct-values($auction/site/people/person/profile/interest/@category) " + + "let $p := for $t in $auction/site/people/person where $t/profile/interest/@category = $i " + + "return {$t/profile/gender/text()} " + + "{$t/profile/age/text()} {$t/profile/education/text()} " + + "{fn:data($t/profile/@income)} " + + "{$t/name/text()} {$t/address/street/text()} " + + "{$t/address/city/text()} {$t/address/country/text()} " + + " {$t/emailaddress/text()} " + + "{$t/homepage/text()} " + + "{$t/creditcard/text()} " + + "return {{$i}, $p}", + "let $auction := . return for $p in $auction/site/people/person let $l := " + + "for $i in $auction/site/open_auctions/open_auction/initial " + + "where $p/profile/@income > 5000 * exactly-one($i/text()) " + + "return $i return {count($l)}", + "let $auction := . return for $p in $auction/site/people/person let $l := " + + "for $i in $auction/site/open_auctions/open_auction/initial " + + "where $p/profile/@income > 5000 * exactly-one($i/text()) return $i " + + "where $p/profile/@income > 50000 " + + "return {count($l)}", + "let $auction := . return for $i in $auction/site/regions/australia/item " + + "return {$i/description}", + "let $auction := . return for $i in $auction/site//item " + + "where contains(string(exactly-one($i/description)), \"gold\") return $i/name/text()", + "let $auction := . return for $a in $auction/site/closed_auctions/closed_auction/annotation/" + + "description/parlist/listitem/parlist/listitem/text/emph/keyword/text() " + + "return {$a}", + "let $auction := . return for $a in $auction/site/closed_auctions/closed_auction " + + "where not( empty( $a/annotation/description/parlist/listitem/parlist/listitem/text/emph/" + + "keyword/text() ) ) return ", + "let $auction := . return for $p in $auction/site/people/person " + + "where empty($p/homepage/text()) return ", + "declare namespace local = \"http://www.foobar.org\"; declare function " + + "local:convert($v as xs:decimal?) as xs:decimal? { 2.20371 * $v (: convert Dfl to Euro :) }; " + + "let $auction := . return for $i in $auction/site/open_auctions/open_auction " + + "return local:convert(zero-or-one($i/reserve))", + "let $auction := . return for $b in $auction/site/regions//item let $k := $b/name/text() " + + "order by zero-or-one($b/location) ascending empty greatest return " + + "{$b/location/text()}", + "let $auction := . return {count($auction/site/people/person/profile" + + "[@income >= 100000])} { count( $auction/site/people/person/" + + "profile[@income < 100000 and @income >= 30000] ) } {" + + "count($auction/site/people/person/profile[@income < 30000])} { " + + "count( for $p in $auction/site/people/person where empty($p/profile/@income) return $p ) } " + + " " + }; + /** Queries to exclude. */ + private static final IntList EXCLUDE = new IntList().add(8, 9, 10, 11, 12); + + /** Server flag. */ + private static BaseXServer server; + + /** + * Initializes the tests. + * @throws Exception any exception + */ + @BeforeClass + public static void init() throws Exception { + // only start server if it is not already running + if(!BaseXServer.ping(StaticOptions.HOST.value(), StaticOptions.PORT.value())) + server = new BaseXServer(); + try(final ClientSession cs = createClient()) { + if(!Boolean.valueOf(cs.query("db:exists('" + DB + "')").execute())) { + throw new BaseXException("Database '" + DB + "' was not found."); + } + } + } + + /** + * Initializes the tests. + * @throws Exception any exception + */ + @BeforeClass + public static void close() throws Exception { + // only stop server if it has not been running before starting the tests + if(server != null) server.stop(); + } + + + /** + * Runs all tests and generates some test output. + * @throws Exception any exception + */ + @Test + public void test() throws Exception { + final TokenBuilder tb = new TokenBuilder().add(DB).add(Prop.NL); + + try(final ClientSession cs = createClient()) { + cs.execute(new Open(DB)); + + // ignore first run + System.out.println("Warming up..."); + for(int i = 1; i <= 20; i++) { + if(!EXCLUDE.contains(i)) { + try(final ClientQuery cq = cs.query(QUERIES[i - 1])) { + final Performance p = new Performance(); + cq.execute(); + System.out.println(i + ": " + p); + } + } + } + + System.out.println(Prop.NL + "Testing..."); + for(int i = 1; i <= 20; i++) { + tb.add(String.format("%02d", i)).add(" "); + final BigDecimal max = BigDecimal.valueOf(MAX); + try(final ClientQuery cq = cs.query(QUERIES[i - 1])) { + if(EXCLUDE.contains(i)) { + tb.add("1000000"); + } else { + double min = Double.MAX_VALUE; + BigDecimal total = BigDecimal.valueOf(0); + int r = 0; + while(total.compareTo(max) < 0) { + final Performance p = new Performance(); + cq.execute(); + final double t = Double.parseDouble(p.getTime().replaceAll(" .*", "")); + total = total.add(BigDecimal.valueOf(t)); + min = Math.min(min, t); + r++; + } + tb.add(Double.toString(min)); + System.out.println(i + ": " + min + " (" + r + " runs, stopped at " + total + " ms)"); + } + } + tb.add(Prop.NL); + } + } + + DIR.md(); + FILE.write(tb.finish()); + } + + /** + * Creates a client instance. + * @return client instance + * @throws IOException I/O exception + */ + private static ClientSession createClient() throws IOException { + return new ClientSession(S_LOCALHOST, StaticOptions.PORT.value(), + UserText.ADMIN, UserText.ADMIN); + } +} diff -Nru basex-8.1.1/basex-tests/src/test/java/org/basex/SandboxTest.java basex-8.2.3/basex-tests/src/test/java/org/basex/SandboxTest.java --- basex-8.1.1/basex-tests/src/test/java/org/basex/SandboxTest.java 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/basex-tests/src/test/java/org/basex/SandboxTest.java 2015-07-14 10:54:40.000000000 +0000 @@ -1,19 +1,6 @@ package org.basex; -import static org.basex.core.Text.*; -import static org.junit.Assert.*; - -import java.io.*; -import java.util.concurrent.*; - -import org.basex.api.client.*; import org.basex.core.*; -import org.basex.core.users.*; -import org.basex.io.*; -import org.basex.io.out.*; -import org.basex.util.*; -import org.basex.util.list.*; -import org.basex.util.options.*; import org.junit.*; /** @@ -22,166 +9,20 @@ * @author BaseX Team 2005-15, BSD License * @author Christian Gruen */ -public abstract class SandboxTest { - /** Database port. */ - protected static final int DB_PORT = 9996; - /** Event port. */ - protected static final int EVENT_PORT = 9997; - - /** Default output stream. */ - public static final PrintStream OUT = System.out; - /** Default error stream. */ - public static final PrintStream ERR = System.err; - /** Null output stream. */ - public static final PrintStream NULL = new PrintStream(new NullOutput()); - /** Test name. */ - protected static final String NAME = Util.className(SandboxTest.class); - /** Database context. */ - protected static Context context; - +public abstract class SandboxTest extends Sandbox { /** * Creates the sandbox. */ @BeforeClass - public static void initSandbox() { - final IOFile sb = sandbox(); - sb.delete(); - assertTrue("Sandbox could not be created.", sb.md()); - context = newContext(); + public static void initTests() { + initSandbox(); } /** * Removes test databases and closes the database context. */ @AfterClass - public static void closeContext() { - context.close(); - assertTrue("Sandbox could not be deleted.", sandbox().delete()); - } - - /** - * Creates a new specified context. - * @return context - */ - public static Context newContext() { - final IOFile sb = sandbox(); - Options.setSystem(StaticOptions.DBPATH.name(), sb.path() + "/data"); - Options.setSystem(StaticOptions.WEBPATH.name(), sb.path() + "/webapp"); - Options.setSystem(StaticOptions.RESTXQPATH.name(), sb.path() + "/webapp"); - Options.setSystem(StaticOptions.REPOPATH.name(), sb.path() + "/repo"); - try { - return new Context(); - } finally { - Options.setSystem(StaticOptions.DBPATH.name(), ""); - Options.setSystem(StaticOptions.WEBPATH.name(), ""); - Options.setSystem(StaticOptions.RESTXQPATH.name(), ""); - Options.setSystem(StaticOptions.REPOPATH.name(), ""); - } - } - - /** - * Creates a new, sandboxed server instance. - * @param args additional arguments - * @return server instance - * @throws IOException I/O exception - */ - public static BaseXServer createServer(final String... args) throws IOException { - try { - System.setOut(NULL); - final StringList sl = new StringList("-z", "-p" + DB_PORT, "-e" + EVENT_PORT, "-q"); - for(final String arg : args) sl.add(arg); - final BaseXServer server = new BaseXServer(sl.finish()); - server.context.soptions.set(StaticOptions.DBPATH, sandbox().path()); - return server; - } finally { - System.setOut(OUT); - } - } - - /** - * Stops a server instance. - * @param server server - * @throws IOException I/O exception - */ - public static void stopServer(final BaseXServer server) throws IOException { - try { - System.setOut(NULL); - if(server != null) server.stop(); - } finally { - System.setOut(OUT); - } - } - - /** - * Creates a client instance. - * @param login optional login data - * @return client instance - * @throws IOException I/O exception - */ - public static ClientSession createClient(final String... login) throws IOException { - final String user = login.length > 0 ? login[0] : UserText.ADMIN; - final String pass = login.length > 1 ? login[1] : UserText.ADMIN; - return new ClientSession(S_LOCALHOST, DB_PORT, user, pass); - } - - /** - * Returns the sandbox database path. - * @return database path - */ - public static IOFile sandbox() { - return new IOFile(Prop.TMP, NAME); - } - - /** - * Normalizes newlines in a query result. - * @param result input string - * @return normalized string - */ - public static String normNL(final Object result) { - return result.toString().replaceAll("(\r?\n|\r) *", "\n"); - } - - /** Client. */ - public static final class Client extends Thread { - /** Start signal. */ - private final CountDownLatch startSignal; - /** Stop signal. */ - private final CountDownLatch stopSignal; - /** Client session. */ - private final ClientSession session; - /** Command string. */ - private final Command cmd; - /** Fail flag. */ - public String error; - - /** - * Client constructor. - * @param c command string to execute - * @param start start signal - * @param stop stop signal - * @throws IOException I/O exception while establishing the session - */ - public Client(final Command c, final CountDownLatch start, final CountDownLatch stop) - throws IOException { - - session = createClient(); - cmd = c; - startSignal = start; - stopSignal = stop; - start(); - } - - @Override - public void run() { - try { - if(startSignal != null) startSignal.await(); - session.execute(cmd); - session.close(); - } catch(final Throwable ex) { - error = "\n" + cmd + '\n' + ex; - } finally { - if(stopSignal != null) stopSignal.countDown(); - } - } + public static void finishTests() { + finishSandbox(); } } diff -Nru basex-8.1.1/CHANGELOG basex-8.2.3/CHANGELOG --- basex-8.1.1/CHANGELOG 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/CHANGELOG 2015-07-14 10:54:40.000000000 +0000 @@ -1,3 +1,47 @@ +VERSION 8.2.3 (July 14, 2015) ------------------------------------------ + + Bug fixes (DBA, admin:write-log; namespaces) + +VERSION 8.2.2 (July 6, 2015) ------------------------------------------- + + Various bug fixes (all minor) + +VERSION 8.2.1 (June 9, 2015) ------------------------------------------- + + DBA + - code highlighting (thanks, James Ball!) + - new panel for up- and downloading files + - queries and files are now stored in a temporary directory + + GENERAL + - various bug fixes + +VERSION 8.2 (May 21, 2015) --------------------------------------------- + + XQUERY + - much faster sequence modification via finger trees + - improved compliance with XQuery 3.1 + + DBA + - open, save and delete queries + - better Tomcat support + + STORAGE + - updatable index structures: reduced disk space consumption + + XQUERY FUNCTIONS + - Standard Module: fn:json-to-xml, fn:xml-to-json + - Web Module: web:encode-url, web:decode-url + - File Module: file:is-absolute, file:resolve-path + - Admin Module: admin:delete-logs + - Database Module: db:output-cache + + BUG FIXES + - locking, full-text requests, stemming + + REMOVED FEATURES + - event handling (will be replaced by database triggers) + VERSION 8.1.1 (April 16, 2015) ----------------------------------------- Various bug fixes (all minor) diff -Nru basex-8.1.1/debian/changelog basex-8.2.3/debian/changelog --- basex-8.1.1/debian/changelog 2015-04-16 20:44:10.000000000 +0000 +++ basex-8.2.3/debian/changelog 2015-07-20 07:49:20.000000000 +0000 @@ -1,3 +1,55 @@ +basex (8.2.3-1) unstable; urgency=medium + + * New upstream releases + VERSION 8.2.3 (July 14, 2015) ------------------------------------------ + + Bug fixes (DBA, admin:write-log; namespaces) + + VERSION 8.2.2 (July 6, 2015) ------------------------------------------- + + Various bug fixes (all minor) + + VERSION 8.2.1 (June 9, 2015) ------------------------------------------- + + DBA + - code highlighting (thanks, James Ball!) + - new panel for up- and downloading files + - queries and files are now stored in a temporary directory + + GENERAL + - various bug fixes + + VERSION 8.2 (May 21, 2015) --------------------------------------------- + + XQUERY + - much faster sequence modification via finger trees + - improved compliance with XQuery 3.1 + + DBA + - open, save and delete queries + - better Tomcat support + + STORAGE + - updatable index structures: reduced disk space consumption + + XQUERY FUNCTIONS + - Standard Module: fn:json-to-xml, fn:xml-to-json + - Web Module: web:encode-url, web:decode-url + - File Module: file:is-absolute, file:resolve-path + - Admin Module: admin:delete-logs + - Database Module: db:output-cache + + BUG FIXES + - locking, full-text requests, stemming + + REMOVED FEATURES + - event handling (will be replaced by database triggers) + + * Packaging: + - Added jing as recommended package. Updated startup scripts accordingly. + + -- Alexander Holupirek Fri, 17 Jul 2015 09:03:33 +0200 + basex (8.1.1-1) unstable; urgency=medium * New upstream release diff -Nru basex-8.1.1/debian/control basex-8.2.3/debian/control --- basex-8.1.1/debian/control 2015-03-20 14:07:39.000000000 +0000 +++ basex-8.2.3/debian/control 2015-07-17 09:54:01.000000000 +0000 @@ -11,9 +11,9 @@ Package: basex Architecture: all -Depends: ${misc:Depends}, ${maven:Depends}, java-wrappers, default-jre | java6-runtime +Depends: ${misc:Depends}, ${maven:Depends}, java-wrappers, default-jre | java7-runtime Recommends: ${maven:OptionalDepends} -Suggests: libtagsoup-java, libxml-commons-resolver1.1-java, libjline2-java +Suggests: libtagsoup-java, libxml-commons-resolver1.1-java, libjline2-java, libjing-java Description: XML database and XPath/XQuery processor BaseX is a very fast and light-weight, yet powerful XML database and XPath/XQuery processor, including support for the latest W3C Full Text and diff -Nru basex-8.1.1/debian/man/basex.1 basex-8.2.3/debian/man/basex.1 --- basex-8.1.1/debian/man/basex.1 2015-04-16 20:39:03.000000000 +0000 +++ basex-8.2.3/debian/man/basex.1 2015-07-20 07:46:43.000000000 +0000 @@ -1,12 +1,12 @@ -.\"Text automatically generated by txt2man -.TH basex 1 "16 April 2015" "" "The XML Database" +.\" Text automatically generated by txt2man +.TH basex 1 "17 July 2015" "" "The XML Database" .SH NAME \fBbasex \fP- XML database system and XPath/XQuery processor (command line mode) \fB .SH SYNOPSIS .nf .fam C -\fBbasex\fP [\fB-bcdioqrRsuvVwxXz\fP] [\fIinput\fP] +\fBbasex\fP [\fB-bcdiIoqrRstuvVwxXz\fP] [\fIinput\fP] .fam T .fi @@ -45,7 +45,7 @@ .fam C $ basex 19+23 42 - $ basex -q "{ 23+19 }" + $ basex \-q "{ 23+19 }" 42 .fam T @@ -237,7 +237,7 @@ Alternative: - $ basex -q 'declare option db:parser "html"; doc("bad.html")' + $ basex \-q 'declare option db:parser "html"; doc("bad.html")'
      diff -Nru basex-8.1.1/debian/man/basex.1e basex-8.2.3/debian/man/basex.1e --- basex-8.1.1/debian/man/basex.1e 2015-04-16 20:39:03.000000000 +0000 +++ basex-8.2.3/debian/man/basex.1e 2015-07-17 10:48:11.000000000 +0000 @@ -1,12 +1,12 @@ -.\"Text automatically generated by txt2man -.TH basex 1 "16 April 2015" "" "The XML Database" +.\" Text automatically generated by txt2man +.TH basex 1 "17 July 2015" "" "The XML Database" .SH NAME \fBbasex \fP- XML database system and XPath/XQuery processor (command line mode) \fB .SH SYNOPSIS .nf .fam C -\fBbasex\fP [\fB-bcdioqrRsuvVwxXz\fP] [\fIinput\fP] +\fBbasex\fP [\fB-bcdiIoqrRstuvVwxXz\fP] [\fIinput\fP] .fam T .fi diff -Nru basex-8.1.1/debian/man/basexclient.1 basex-8.2.3/debian/man/basexclient.1 --- basex-8.1.1/debian/man/basexclient.1 2015-04-16 20:39:03.000000000 +0000 +++ basex-8.2.3/debian/man/basexclient.1 2015-07-17 10:48:11.000000000 +0000 @@ -1,12 +1,12 @@ -.\"Text automatically generated by txt2man -.TH basexclient 1 "16 April 2015" "" "The XML Database" +.\" Text automatically generated by txt2man +.TH basexclient 1 "17 July 2015" "" "The XML Database" .SH NAME \fBbasexclient \fP- XML database system and XPath/XQuery processor (client mode) \fB .SH SYNOPSIS .nf .fam C -\fBbasexclient\fP [\fB-bcdinopPqrRsUvVwxz\fP] [\fIinput\fP] +\fBbasexclient\fP [\fB-bcdiInopPqrRsUvVwxXz\fP] [\fIinput\fP] .fam T .fi diff -Nru basex-8.1.1/debian/man/basexclient.txt basex-8.2.3/debian/man/basexclient.txt --- basex-8.1.1/debian/man/basexclient.txt 2015-04-16 20:38:12.000000000 +0000 +++ basex-8.2.3/debian/man/basexclient.txt 2015-07-17 10:47:25.000000000 +0000 @@ -2,7 +2,7 @@ basexclient - XML database system and XPath/XQuery processor (client mode) SYNOPSIS - basexclient [-bcdinopPqrRsUvVwxz] [input] + basexclient [-bcdiInopPqrRsUvVwxXz] [input] DESCRIPTION basexclient starts a basex(1) XML database client in order to connect to a diff -Nru basex-8.1.1/debian/man/basexgui.1 basex-8.2.3/debian/man/basexgui.1 --- basex-8.1.1/debian/man/basexgui.1 2015-04-16 20:39:03.000000000 +0000 +++ basex-8.2.3/debian/man/basexgui.1 2015-07-17 10:48:11.000000000 +0000 @@ -1,5 +1,5 @@ -.\"Text automatically generated by txt2man -.TH basexgui 1 "16 April 2015" "" "The XML Database" +.\" Text automatically generated by txt2man +.TH basexgui 1 "17 July 2015" "" "The XML Database" .SH NAME \fBbasexgui \fP- XML database system and XPath/XQuery processor (graphical mode) \fB diff -Nru basex-8.1.1/debian/man/basexserver.1 basex-8.2.3/debian/man/basexserver.1 --- basex-8.1.1/debian/man/basexserver.1 2015-04-16 20:39:03.000000000 +0000 +++ basex-8.2.3/debian/man/basexserver.1 2015-07-17 10:48:11.000000000 +0000 @@ -1,12 +1,12 @@ -.\"Text automatically generated by txt2man -.TH basexserver 1 "16 April 2015" "" "The XML Database" +.\" Text automatically generated by txt2man +.TH basexserver 1 "17 July 2015" "" "The XML Database" .SH NAME \fBbasexserver \fP- XML database system and XPath/XQuery processor (server mode) \fB .SH SYNOPSIS .nf .fam C -\fBbasexserver\fP [\fB-cdeinpSz\fP] [\fIstop\fP] +\fBbasexserver\fP [\fB-cdinpSz\fP] [\fIstop\fP] .fam T .fi diff -Nru basex-8.1.1/debian/man/basexserver.txt basex-8.2.3/debian/man/basexserver.txt --- basex-8.1.1/debian/man/basexserver.txt 2015-04-16 20:38:38.000000000 +0000 +++ basex-8.2.3/debian/man/basexserver.txt 2015-07-17 10:47:51.000000000 +0000 @@ -2,7 +2,7 @@ basexserver - XML database system and XPath/XQuery processor (server mode) SYNOPSIS - basexserver [-cdeinpSz] [stop] + basexserver [-cdinpSz] [stop] DESCRIPTION basexserver starts the server mode of the native XML database system basex(1) on default port 1984. diff -Nru basex-8.1.1/debian/man/basex.txt basex-8.2.3/debian/man/basex.txt --- basex-8.1.1/debian/man/basex.txt 2015-04-16 20:36:31.000000000 +0000 +++ basex-8.2.3/debian/man/basex.txt 2015-07-17 10:46:59.000000000 +0000 @@ -2,7 +2,7 @@ basex - XML database system and XPath/XQuery processor (command line mode) SYNOPSIS - basex [-bcdioqrRsuvVwxXz] [input] + basex [-bcdiIoqrRstuvVwxXz] [input] DESCRIPTION basex is a fast and powerful, yet light-weight and platform independent XML diff -Nru basex-8.1.1/debian/maven.ignoreRules basex-8.2.3/debian/maven.ignoreRules --- basex-8.1.1/debian/maven.ignoreRules 2015-04-16 20:42:51.000000000 +0000 +++ basex-8.2.3/debian/maven.ignoreRules 2015-07-20 10:03:33.000000000 +0000 @@ -1,5 +1,6 @@ org.basex basex-api jar * * * +com.thaiopensource jing * * * * jp.sourceforge.igo igo * * * * junit junit * * * * org.apache lucene-stemmers * * * * diff -Nru basex-8.1.1/debian/scripts/basex basex-8.2.3/debian/scripts/basex --- basex-8.1.1/debian/scripts/basex 2013-10-17 13:48:50.000000000 +0000 +++ basex-8.2.3/debian/scripts/basex 2015-07-17 09:53:15.000000000 +0000 @@ -1,19 +1,20 @@ #!/bin/sh # Wrapper to start basex in command line mode. # -# (c) 2011-13 Alexander Holupirek , BSD +# (c) 2011-15 Alexander Holupirek , BSD # #DEBUG_WRAPPER=1 . /usr/lib/java-wrappers/java-wrappers.sh -find_java_runtime java6 +find_java_runtime java7 find_jars /usr/share/java/basex.jar -# Next jars are helpful (and as such recommended by the package), +# Next jars are helpful (and therefore recommended by the package), # but they are not necessary at all to run basex. As such, there is no # need to print a warning message, if they are not found find_jars /usr/share/java/tagsoup.jar 2>/dev/null find_jars /usr/share/java/jline.jar 2>/dev/null find_jars /usr/share/java/xml-resolver.jar 2>/dev/null +find_jars /usr/share/java/jing.jar 2>/dev/null run_java org.basex.BaseX "$@" diff -Nru basex-8.1.1/debian/scripts/basexclient basex-8.2.3/debian/scripts/basexclient --- basex-8.1.1/debian/scripts/basexclient 2013-10-17 13:48:08.000000000 +0000 +++ basex-8.2.3/debian/scripts/basexclient 2015-07-17 09:53:15.000000000 +0000 @@ -1,20 +1,20 @@ #!/bin/sh # Wrapper to start basex database client. # -# (c) 2011-13 Alexander Holupirek , BSD +# (c) 2011-15 Alexander Holupirek , BSD # #DEBUG_WRAPPER=1 . /usr/lib/java-wrappers/java-wrappers.sh -find_java_runtime java6 +find_java_runtime java7 find_jars /usr/share/java/basex.jar -# Next jars are helpful (and as such recommended by the package), +# Next jars are helpful (and therefore recommended by the package), # but they are not necessary at all to run basex. As such, there is no # need to print a warning message, if they are not found find_jars /usr/share/java/tagsoup.jar 2>/dev/null find_jars /usr/share/java/jline.jar 2>/dev/null find_jars /usr/share/java/xml-resolver.jar 2>/dev/null - +find_jars /usr/share/java/jing.jar 2>/dev/null run_java org.basex.BaseXClient "$@" diff -Nru basex-8.1.1/debian/scripts/basexgui basex-8.2.3/debian/scripts/basexgui --- basex-8.1.1/debian/scripts/basexgui 2013-10-17 13:47:22.000000000 +0000 +++ basex-8.2.3/debian/scripts/basexgui 2015-07-17 09:53:15.000000000 +0000 @@ -1,19 +1,20 @@ #!/bin/sh # Wrapper to start basex gui mode. # -# (c) 2011-13 Alexander Holupirek , BSD +# (c) 2011-15 Alexander Holupirek , BSD # #DEBUG_WRAPPER=1 . /usr/lib/java-wrappers/java-wrappers.sh -find_java_runtime java6 +find_java_runtime java7 find_jars /usr/share/java/basex.jar -# Next jars are helpful (and as such recommended by the package), +# Next jars are helpful (and therefore recommended by the package), # but they are not necessary at all to run basex. As such, there is no # need to print a warning message, if they are not found find_jars /usr/share/java/tagsoup.jar 2>/dev/null find_jars /usr/share/java/jline.jar 2>/dev/null find_jars /usr/share/java/xml-resolver.jar 2>/dev/null +find_jars /usr/share/java/jing.jar 2>/dev/null run_java org.basex.BaseXGUI "$@" diff -Nru basex-8.1.1/debian/scripts/basexserver basex-8.2.3/debian/scripts/basexserver --- basex-8.1.1/debian/scripts/basexserver 2013-10-17 13:48:49.000000000 +0000 +++ basex-8.2.3/debian/scripts/basexserver 2015-07-17 09:53:15.000000000 +0000 @@ -1,19 +1,20 @@ #!/bin/sh # Wrapper to start basex as database server. # -# (c) 2011-13 Alexander Holupirek , BSD +# (c) 2011-15 Alexander Holupirek , BSD # #DEBUG_WRAPPER=1 . /usr/lib/java-wrappers/java-wrappers.sh -find_java_runtime java6 +find_java_runtime java7 find_jars /usr/share/java/basex.jar -# Next jars are helpful (and as such recommended by the package), +# Next jars are helpful (and therefore recommended by the package), # but they are not necessary at all to run basex. As such, there is no # need to print a warning message, if they are not found find_jars /usr/share/java/tagsoup.jar 2>/dev/null find_jars /usr/share/java/jline.jar 2>/dev/null find_jars /usr/share/java/xml-resolver.jar 2>/dev/null +find_jars /usr/share/java/jing.jar 2>/dev/null run_java org.basex.BaseXServer "$@" diff -Nru basex-8.1.1/pom.xml basex-8.2.3/pom.xml --- basex-8.1.1/pom.xml 2015-04-16 13:52:47.000000000 +0000 +++ basex-8.2.3/pom.xml 2015-07-14 10:54:40.000000000 +0000 @@ -5,7 +5,7 @@ org.basex basex-parent - 8.1.1 + 8.2.3 pom @@ -65,6 +65,29 @@ true + com.thaiopensource + jing + 20091111 + + + xerces + xercesImpl + + + xml-apis + xml-apis + + + net.sf.saxon + saxon + + + isorelax + isorelax + + + + net.xqj basex-xqj 1.4.0