/** * Checks if data can be updated. * * @param n node instance * @param no disallowed node types * @return result of check */ static boolean updatable(final Nodes 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]); for (final int i : no) if (k == i) return false; return true; }
@Override public void refreshMark() { final EditorArea edit = getEditor(); go.setEnabled(edit.script || edit.xquery && !gui.gprop.is(GUIProp.EXECRT)); final Nodes mrk = gui.context.marked; filter.setEnabled(!gui.gprop.is(GUIProp.FILTERRT) && mrk != null && mrk.size() != 0); }
/** * Returns the resulting query text (text node or attribute value). * * @param qu query * @param root root node * @return attribute value * @throws Exception exception */ protected String text(final String qu, final Nodes root) throws Exception { final Nodes n = nodes(qu, root); final TokenBuilder tb = new TokenBuilder(); for (int i = 0; i < n.size(); ++i) { if (i != 0) tb.add('/'); tb.add(data.atom(n.list[i])); } return tb.toString(); }
@Override public void execute(final GUI gui) { final StringBuilder sb = new StringBuilder(); final Nodes n = gui.context.copied; for (int i = 0; i < n.size(); ++i) { if (i > 0) sb.append(','); sb.append(openPre(n, i)); } gui.context.copied = null; gui.execute(new XQuery("insert nodes (" + sb + ") into " + openPre(gui.context.marked, 0))); }
@Override public void execute(final GUI gui) { final Context ctx = gui.context; Nodes marked = ctx.marked; if (marked.size() == 0) { final int pre = gui.context.focused; if (pre == -1) return; marked = new Nodes(pre, ctx.data()); } gui.notify.context(marked, false, null); }
@Override public void execute(final GUI gui) { if (!BaseXDialog.confirm(gui, DELETE_NODES)) return; final StringBuilder sb = new StringBuilder(); final Nodes n = gui.context.marked; for (int i = 0; i < n.size(); ++i) { if (i > 0) sb.append(','); sb.append(openPre(n, i)); } gui.context.marked = new Nodes(n.data); gui.context.copied = null; gui.context.focused = -1; gui.execute(new XQuery("delete nodes (" + sb + ')')); }
/** * Notifies all views of a selection change. The mode flag determines what happens: * * <ul> * <li>0: set currently focused node as marked node * <li>1: add currently focused node * <li>2: toggle currently focused node * </ul> * * @param mode mark mode * @param vw the calling view */ public void mark(final int mode, final View vw) { final int f = gui.context.focused; if (f == -1) return; final Context ctx = gui.context; Nodes nodes = ctx.marked; if (mode == 0) { nodes = new Nodes(f, ctx.data()); } else if (mode == 1) { nodes.union(new int[] {f}); } else { nodes.toggle(f); } mark(nodes, vw); }
/** * Initializes the input files, specified by the context nodes. * * @param nod variables * @param var documents * @param qp query processor * @param first call * @return string with input files * @throws Exception exception */ private byte[] file( final Nodes nod, final Nodes var, final QueryProcessor qp, final boolean first) throws Exception { final TokenBuilder tb = new TokenBuilder(); for (int c = 0; c < nod.size(); ++c) { final byte[] nm = data.atom(nod.list[c]); String src = srcs.get(string(nm)); if (tb.size() != 0) tb.add(", "); tb.add(nm); Expr expr = null; if (src == null) { // assign collection expr = coll(nm, qp); } else { // assign document final String dbname = new IOFile(src).dbname(); Function def = Function.DOC; // updates: drop updated document or open updated database if (updating()) { if (first) { new DropDB(dbname).execute(context); } else { def = Function.OPEN; src = dbname; } } expr = def.get(null, Str.get(src)); } if (var != null) qp.bind(string(data.atom(var.list[c])), expr); } return tb.finish(); }
/** * Notifies all views of a context change. * * @param nodes new context set (may be {@code null} if root nodes are addressed) * @param quick quick switch * @param vw the calling view */ public void context(final Nodes nodes, final boolean quick, final View vw) { final Context ctx = gui.context; // add new entry if current node set has not been cached yet final Nodes newn = nodes.checkRoot(); final Nodes empty = new Nodes(new int[0], ctx.data(), ctx.marked.ftpos); final Nodes curr = quick ? ctx.current() : null; final Nodes cmp = quick ? curr : ctx.marked; if (cont[hist] == null ? cmp != null : cmp == null || !cont[hist].sameAs(cmp)) { checkHist(); if (quick) { // store history entry queries[hist] = ""; marked[hist] = new Nodes(ctx.data()); // add current entry cont[++hist] = curr; } else { // store history entry final String in = gui.input.getText(); queries[hist] = in; marked[hist] = ctx.marked; // add current entry cont[++hist] = newn; queries[hist] = in; marked[hist] = empty; } histsize = hist; } ctx.set(newn, empty); for (final View v : view) if (v != vw && v.visible()) v.refreshContext(true, quick); gui.refreshControls(); }
/** * Notifies all views of a selection change. * * @param mark marked nodes * @param vw the calling view */ public void mark(final Nodes mark, final View vw) { final Context ctx = gui.context; ctx.marked = mark; for (final View v : view) if (v != vw && v.visible()) v.refreshMark(); gui.filter.setEnabled(mark.size() != 0); gui.refreshControls(); }
/** * Assigns the nodes to the specified variables. * * @param nod nodes * @param var variables * @param qp query processor * @throws QueryException query exception */ private void var(final Nodes nod, final Nodes var, final QueryProcessor qp) throws QueryException { for (int c = 0; c < nod.size(); ++c) { final byte[] nm = data.atom(nod.list[c]); final String src = srcs.get(string(nm)); final Item it = src == null ? coll(nm, qp) : Str.get(src); qp.bind(string(data.atom(var.list[c])), it); } }
/** * Evaluates the the input files and assigns the result to the specified variables. * * @param nod variables * @param var documents * @param pth file path * @param qp query processor * @throws Exception exception */ private void eval(final Nodes nod, final Nodes var, final String pth, final QueryProcessor qp) throws Exception { for (int c = 0; c < nod.size(); ++c) { final String file = pth + string(data.atom(nod.list[c])) + IO.XQSUFFIX; final String in = read(new IOFile(queries + file)); final QueryProcessor xq = new QueryProcessor(in, context); final Value val = xq.value(); qp.bind(string(data.atom(var.list[c])), val); xq.close(); } }
/** * Parses the specified test case. * * @param root root node * @throws Exception exception * @return true if the query, specified by {@link #single}, was evaluated */ private boolean parse(final Nodes root) throws Exception { final String pth = text("@FilePath", root); final String outname = text("@name", root); if (single != null && !outname.startsWith(single)) return true; final Performance perf = new Performance(); if (verbose) Util.out("- " + outname); boolean inspect = false; boolean correct = true; final Nodes nodes = states(root); for (int n = 0; n < nodes.size(); ++n) { final Nodes state = new Nodes(nodes.list[n], nodes.data); final String inname = text("*:query/@name", state); context.query = new IOFile(queries + pth + inname + IO.XQSUFFIX); final String in = read(context.query); String er = null; ItemCache iter = null; boolean doc = true; final Nodes cont = nodes("*:contextItem", state); Nodes curr = null; if (cont.size() != 0) { final Data d = Check.check(context, srcs.get(string(data.atom(cont.list[0])))); curr = new Nodes(d.doc(), d); curr.root = true; } context.prop.set(Prop.QUERYINFO, compile); final QueryProcessor xq = new QueryProcessor(in, curr, context); context.prop.set(Prop.QUERYINFO, false); // limit result sizes to 1MB final ArrayOutput ao = new ArrayOutput(); final TokenBuilder files = new TokenBuilder(); try { files.add( file(nodes("*:input-file", state), nodes("*:input-file/@variable", state), xq, n == 0)); files.add(file(nodes("*:defaultCollection", state), null, xq, n == 0)); var(nodes("*:input-URI", state), nodes("*:input-URI/@variable", state), xq); eval(nodes("*:input-query/@name", state), nodes("*:input-query/@variable", state), pth, xq); parse(xq, state); for (final int p : nodes("*:module", root).list) { final String uri = text("@namespace", new Nodes(p, data)); final String file = mods.get(string(data.atom(p))) + IO.XQSUFFIX; xq.module(file, uri); } // evaluate and serialize query final SerializerProp sp = new SerializerProp(); sp.set(SerializerProp.S_INDENT, context.prop.is(Prop.CHOP) ? DataText.YES : DataText.NO); final XMLSerializer xml = new XMLSerializer(ao, sp); iter = xq.value().cache(); for (Item it; (it = iter.next()) != null; ) { doc &= it.type == NodeType.DOC; it.serialize(xml); } xml.close(); } catch (final Exception ex) { if (!(ex instanceof QueryException || ex instanceof IOException)) { System.err.println("\n*** " + outname + " ***"); System.err.println(in + "\n"); ex.printStackTrace(); } er = ex.getMessage(); if (er.startsWith(STOPPED)) er = er.substring(er.indexOf('\n') + 1); if (er.startsWith("[")) er = er.replaceAll("\\[(.*?)\\] (.*)", "$1 $2"); // unexpected error - dump stack trace } // print compilation steps if (compile) { Util.errln("---------------------------------------------------------"); Util.err(xq.info()); Util.errln(in); } final Nodes expOut = nodes("*:output-file/text()", state); final TokenList result = new TokenList(); for (int o = 0; o < expOut.size(); ++o) { final String resFile = string(data.atom(expOut.list[o])); final IOFile exp = new IOFile(expected + pth + resFile); result.add(read(exp)); } final Nodes cmpFiles = nodes("*:output-file/@compare", state); boolean xml = false; boolean frag = false; boolean ignore = false; for (int o = 0; o < cmpFiles.size(); ++o) { final byte[] type = data.atom(cmpFiles.list[o]); xml |= eq(type, XML); frag |= eq(type, FRAGMENT); ignore |= eq(type, IGNORE); } String expError = text("*:expected-error/text()", state); final StringBuilder log = new StringBuilder(pth + inname + IO.XQSUFFIX); if (files.size() != 0) { log.append(" ["); log.append(files); log.append("]"); } log.append(NL); /** Remove comments. */ log.append(norm(in)); log.append(NL); final String logStr = log.toString(); // skip queries with variable results final boolean print = currTime || !logStr.contains("current-"); boolean correctError = false; if (er != null && (expOut.size() == 0 || !expError.isEmpty())) { expError = error(pth + outname, expError); final String code = er.substring(0, Math.min(8, er.length())); for (final String e : SLASH.split(expError)) { if (code.equals(e)) { correctError = true; break; } } } if (correctError) { if (print) { logOK.append(logStr); logOK.append("[Right] "); logOK.append(norm(er)); logOK.append(NL); logOK.append(NL); addLog(pth, outname + ".log", er); } ++ok; } else if (er == null) { int s = -1; final int rs = result.size(); while (!ignore && ++s < rs) { inspect |= s < cmpFiles.list.length && eq(data.atom(cmpFiles.list[s]), INSPECT); final byte[] res = result.get(s), actual = ao.toArray(); if (res.length == ao.size() && eq(res, actual)) break; if (xml || frag) { iter.reset(); try { final ItemCache ic = toIter(string(res).replaceAll("^<\\?xml.*?\\?>", "").trim(), frag); if (FNSimple.deep(null, iter, ic)) break; ic.reset(); final ItemCache ia = toIter(string(actual), frag); if (FNSimple.deep(null, ia, ic)) break; } catch (final Throwable ex) { System.err.println("\n" + outname + ":"); ex.printStackTrace(); } } } if ((rs > 0 || !expError.isEmpty()) && s == rs && !inspect) { if (print) { if (expOut.size() == 0) result.add(error(pth + outname, expError)); logErr.append(logStr); logErr.append("[" + testid + " ] "); logErr.append(norm(string(result.get(0)))); logErr.append(NL); logErr.append("[Wrong] "); logErr.append(norm(ao.toString())); logErr.append(NL); logErr.append(NL); addLog(pth, outname + (xml ? IO.XMLSUFFIX : ".txt"), ao.toString()); } correct = false; ++err; } else { if (print) { logOK.append(logStr); logOK.append("[Right] "); logOK.append(norm(ao.toString())); logOK.append(NL); logOK.append(NL); addLog(pth, outname + (xml ? IO.XMLSUFFIX : ".txt"), ao.toString()); } ++ok; } } else { if (expOut.size() == 0 || !expError.isEmpty()) { if (print) { logOK2.append(logStr); logOK2.append("[" + testid + " ] "); logOK2.append(norm(expError)); logOK2.append(NL); logOK2.append("[Rght?] "); logOK2.append(norm(er)); logOK2.append(NL); logOK2.append(NL); addLog(pth, outname + ".log", er); } ++ok2; } else { if (print) { logErr2.append(logStr); logErr2.append("[" + testid + " ] "); logErr2.append(norm(string(result.get(0)))); logErr2.append(NL); logErr2.append("[Wrong] "); logErr2.append(norm(er)); logErr2.append(NL); logErr2.append(NL); addLog(pth, outname + ".log", er); } correct = false; ++err2; } } if (curr != null) Close.close(curr.data, context); xq.close(); } if (reporting) { logReport.append(" <test-case name=\""); logReport.append(outname); logReport.append("\" result='"); logReport.append(correct ? "pass" : "fail"); if (inspect) logReport.append("' todo='inspect"); logReport.append("'/>"); logReport.append(NL); } // print verbose/timing information final long nano = perf.getTime(); final boolean slow = nano / 1000000 > timer; if (verbose) { if (slow) Util.out(": " + Performance.getTimer(nano, 1)); Util.outln(); } else if (slow) { Util.out(NL + "- " + outname + ": " + Performance.getTimer(nano, 1)); } return single == null || !outname.equals(single); }
/** * Runs the test suite. * * @param args command-line arguments * @throws Exception exception */ void run(final String[] args) throws Exception { final Args arg = new Args( args, this, " Test Suite [options] [pat]" + NL + " [pat] perform only tests with the specified pattern" + NL + " -c print compilation steps" + NL + " -h show this help" + NL + " -m minimum conformance" + NL + " -g <test-group> test group to test" + NL + " -C run tests depending on current time" + NL + " -p change path" + NL + " -r create report" + NL + " -t[ms] list slowest queries" + NL + " -v verbose output"); while (arg.more()) { if (arg.dash()) { final char c = arg.next(); if (c == 'r') { reporting = true; currTime = true; } else if (c == 'C') { currTime = true; } else if (c == 'c') { compile = true; } else if (c == 'm') { minimum = true; } else if (c == 'g') { group = arg.string(); } else if (c == 'p') { path = arg.string() + "/"; } else if (c == 't') { timer = arg.num(); } else if (c == 'v') { verbose = true; } else { arg.check(false); } } else { single = arg.string(); maxout = Integer.MAX_VALUE; } } if (!arg.finish()) return; queries = path + "Queries/XQuery/"; expected = path + "ExpectedTestResults/"; results = path + "ReportingResults/Results/"; report = path + "ReportingResults/"; sources = path + "TestSources/"; final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); final String dat = sdf.format(Calendar.getInstance().getTime()); final Performance perf = new Performance(); context.prop.set(Prop.CHOP, false); // new Check(path + input).execute(context); data = CreateDB.xml(new IOFile(path + input), context); final Nodes root = new Nodes(0, data); Util.outln(NL + Util.name(this) + " Test Suite " + text("/*:test-suite/@version", root)); Util.outln(NL + "Caching Sources..."); for (final int s : nodes("//*:source", root).list) { final Nodes srcRoot = new Nodes(s, data); final String val = (path + text("@FileName", srcRoot)).replace('\\', '/'); srcs.put(text("@ID", srcRoot), val); } Util.outln("Caching Modules..."); for (final int s : nodes("//*:module", root).list) { final Nodes srcRoot = new Nodes(s, data); final String val = (path + text("@FileName", srcRoot)).replace('\\', '/'); mods.put(text("@ID", srcRoot), val); } Util.outln("Caching Collections..."); for (final int c : nodes("//*:collection", root).list) { final Nodes nodes = new Nodes(c, data); final String cname = text("@ID", nodes); final TokenList dl = new TokenList(); final Nodes doc = nodes("*:input-document", nodes); for (int d = 0; d < doc.size(); ++d) { dl.add(token(sources + string(data.atom(doc.list[d])) + IO.XMLSUFFIX)); } colls.put(cname, dl.toArray()); } init(root); if (reporting) { Util.outln("Delete old results..."); delete(new File[] {new File(results)}); } if (verbose) Util.outln(); final Nodes nodes = minimum ? nodes("//*:test-group[starts-with(@name, 'Minim')]//*:test-case", root) : group != null ? nodes("//*:test-group[@name eq '" + group + "']//*:test-case", root) : nodes("//*:test-case", root); long total = nodes.size(); Util.out("Parsing " + total + " Queries"); for (int t = 0; t < total; ++t) { if (!parse(new Nodes(nodes.list[t], data))) break; if (!verbose && t % 500 == 0) Util.out("."); } Util.outln(); total = ok + ok2 + err + err2; final String time = perf.getTimer(); Util.outln("Writing log file..." + NL); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path + pathlog), UTF8)); bw.write("TEST RESULTS =================================================="); bw.write(NL + NL + "Total #Queries: " + total + NL); bw.write("Correct / Empty Results: " + ok + " / " + ok2 + NL); bw.write("Conformance (w/Empty Results): "); bw.write(pc(ok, total) + " / " + pc(ok + ok2, total) + NL); bw.write("Wrong Results / Errors: " + err + " / " + err2 + NL); bw.write("WRONG ========================================================="); bw.write(NL + NL + logErr + NL); bw.write("WRONG (ERRORS) ================================================"); bw.write(NL + NL + logErr2 + NL); bw.write("CORRECT? (EMPTY) =============================================="); bw.write(NL + NL + logOK2 + NL); bw.write("CORRECT ======================================================="); bw.write(NL + NL + logOK + NL); bw.write("==============================================================="); bw.close(); bw = new BufferedWriter(new FileWriter(path + pathhis, true)); bw.write(dat + "\t" + ok + "\t" + ok2 + "\t" + err + "\t" + err2 + NL); bw.close(); if (reporting) { bw = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(report + NAME + IO.XMLSUFFIX), UTF8)); write(bw, report + NAME + "Pre" + IO.XMLSUFFIX); bw.write(logReport.toString()); write(bw, report + NAME + "Pos" + IO.XMLSUFFIX); bw.close(); } Util.outln("Total #Queries: " + total); Util.outln("Correct / Empty results: " + ok + " / " + ok2); Util.out("Conformance (w/empty results): "); Util.outln(pc(ok, total) + " / " + pc(ok + ok2, total)); Util.outln("Total Time: " + time); context.close(); }
/** * Assumes that the nodes have the specified number of nodes. * * @param nodes context nodes * @param size expected size */ private static void ok(final Nodes nodes, final int size) { if (nodes != null) assertEquals(size, nodes.size()); }
@Override public void refresh(final GUI gui, final AbstractButton b) { final Nodes marked = gui.context.marked; b.setEnabled(marked != null && marked.size() != 0); }
@Override public void paintComponent(final Graphics g) { super.paintComponent(g); // skip if view is unavailable if (tdata.rows == null) return; gui.painting = true; g.setFont(GUIConstants.font); final int w = getWidth() - scroll.getWidth(); final int h = getHeight(); final int fsz = gui.gprop.num(GUIProp.FONTSIZE); final Context context = tdata.context; final Data data = context.data(); final int focus = gui.context.focused; final int rfocus = tdata.getRoot(data, focus); int mpos = 0; final int nCols = tdata.cols.length; final int nRows = tdata.rows.size(); final int rowH = tdata.rowH; final TableIterator ti = new TableIterator(data, tdata); final TokenBuilder[] tb = new TokenBuilder[nCols]; for (int i = 0; i < nCols; ++i) tb[i] = new TokenBuilder(); focusedString = null; final Nodes marked = context.marked; int l = scroll.pos() / rowH - 1; int posY = -scroll.pos() + l * rowH; while (++l < nRows && marked != null) { // skip when all visible rows have been painted or if data has changed if (posY > h || l >= tdata.rows.size()) break; posY += rowH; final int pre = tdata.rows.get(l); while (mpos < marked.size() && marked.list[mpos] < pre) ++mpos; // draw line g.setColor(GUIConstants.color2); g.drawLine(0, posY + rowH - 1, w, posY + rowH - 1); g.setColor(Color.white); g.drawLine(0, posY + rowH, w, posY + rowH); // verify if current node is marked or focused final boolean rm = mpos < marked.size() && marked.list[mpos] == pre; final boolean rf = pre == rfocus; final int col = rm ? rf ? 5 : 4 : 3; if (rm || rf) { g.setColor(GUIConstants.color(col)); g.fillRect(0, posY - 1, w, rowH); g.setColor(GUIConstants.color(col + 4)); g.drawLine(0, posY - 1, w, posY - 1); } g.setColor(Color.black); // skip drawing of text during animation if (rowH < fsz) continue; // find all row contents ti.init(pre); int fcol = -1; while (ti.more()) { final int c = ti.col; if (ti.pre == focus || data.parent(ti.pre, data.kind(ti.pre)) == focus) fcol = c; // add content to column (skip too long contents)... if (tb[c].size() < 100) { if (tb[c].size() != 0) tb[c].add("; "); tb[c].add(data.text(ti.pre, ti.text)); } } // add dots if content is too long for (final TokenBuilder t : tb) if (t.size() > 100) t.add(DOTS); // draw row contents byte[] focusStr = null; int fx = -1; double x = 1; for (int c = 0; c < nCols; ++c) { // draw single column final double cw = w * tdata.cols[c].width; final double ce = x + cw; if (ce != 0) { final byte[] str = tb[c].size() != 0 ? tb[c].finish() : null; if (str != null) { if (tdata.mouseX > x && tdata.mouseX < ce || fcol == c) { fx = (int) x; focusStr = str; } BaseXLayout.chopString(g, str, (int) x + 1, posY + 2, (int) cw - 4, fsz); tb[c].reset(); } } x = ce; } // highlight focused entry if (rf || fcol != -1) { if (focusStr != null) { final int sw = BaseXLayout.width(g, focusStr) + 8; if (fx > w - sw - 2) fx = w - sw - 2; g.setColor(GUIConstants.color(col + 2)); g.fillRect(fx - 2, posY, sw, rowH - 1); g.setColor(Color.black); BaseXLayout.chopString(g, focusStr, fx + 1, posY + 2, sw, fsz); // cache focused string focusedString = string(focusStr); final int i = focusedString.indexOf("; "); if (i != -1) focusedString = focusedString.substring(0, i); } } } gui.painting = false; }
@Override public void refresh(final GUI gui, final AbstractButton b) { // disallow copy of empty node set or root node final Nodes marked = gui.context.marked; b.setEnabled(marked != null && marked.size() != 0); }