/* * eXist Open Source Native XML Database * Copyright (C) 2001-2014 The eXist Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Id$ */ package org.exist.dom.persistent; import antlr.collections.AST; import org.exist.EXistException; import org.exist.numbering.NodeId; import org.exist.security.Subject; import org.exist.storage.BrokerPool; import org.exist.storage.DBBroker; import org.exist.util.OrderedLinkedList; import org.exist.xquery.AnalyzeContextInfo; import org.exist.xquery.Constants; import org.exist.xquery.PathExpr; import org.exist.xquery.XPathException; import org.exist.xquery.XQueryContext; import org.exist.xquery.parser.XQueryLexer; import org.exist.xquery.parser.XQueryParser; import org.exist.xquery.parser.XQueryTreeParser; import org.exist.xquery.value.Item; import org.exist.xquery.value.Sequence; import org.exist.xquery.value.SequenceIterator; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.io.StringReader; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Optional; public class SortedNodeSet extends AbstractNodeSet { private final OrderedLinkedList list = new OrderedLinkedList(); private final String sortExpr; private final BrokerPool pool; private final Subject user; public SortedNodeSet(final BrokerPool pool, final Subject user, final String sortExpr) { this.sortExpr = sortExpr; this.pool = pool; this.user = user; } @Override public boolean isEmpty() { return list.size() == 0; } @Override public boolean hasOne() { return list.size() == 1; } @Override public void addAll(final Sequence other) throws XPathException { addAll(other.toNodeSet()); } @Override public void addAll(final NodeSet other) { final long start = System.currentTimeMillis(); final MutableDocumentSet docs = new DefaultDocumentSet(); for(final Iterator<NodeProxy> i = other.iterator(); i.hasNext(); ) { final NodeProxy p = i.next(); docs.add(p.getOwnerDocument()); } // TODO(pkaminsk2): why replicate XQuery.compile here? try(final DBBroker broker = pool.get(Optional.ofNullable(user))) { final XQueryContext context = new XQueryContext(pool); final XQueryLexer lexer = new XQueryLexer(context, new StringReader(sortExpr)); final XQueryParser parser = new XQueryParser(lexer); final XQueryTreeParser treeParser = new XQueryTreeParser(context); parser.xpath(); if(parser.foundErrors()) { //TODO : error ? LOG.debug(parser.getErrorMessage()); } final AST ast = parser.getAST(); LOG.debug("generated AST: " + ast.toStringTree()); final PathExpr expr = new PathExpr(context); treeParser.xpath(ast, expr); if(treeParser.foundErrors()) { LOG.debug(treeParser.getErrorMessage()); } expr.analyze(new AnalyzeContextInfo()); for(final SequenceIterator i = other.iterate(); i.hasNext(); ) { final NodeProxy p = (NodeProxy) i.nextItem(); final IteratorItem item = new IteratorItem(p, expr); list.add(item); } } catch(final antlr.RecognitionException re) { LOG.debug(re); //TODO : throw exception ! -pb } catch(final antlr.TokenStreamException tse) { LOG.debug(tse); //TODO : throw exception ! -pb } catch(final EXistException e) { LOG.debug("Exception during sort", e); //TODO : throw exception ! -pb } catch(final XPathException e) { LOG.debug("Exception during sort", e); //TODO : throw exception ! -pb } LOG.debug("sort-expression found " + list.size() + " in " + (System.currentTimeMillis() - start) + "ms."); } public void addAll(final NodeList other) { if(!(other instanceof NodeSet)) { throw new RuntimeException("not implemented!"); } addAll((NodeSet) other); } @Override public boolean contains(final NodeProxy proxy) { for(final Iterator<IteratorItem> i = list.iterator(); i.hasNext(); ) { final NodeProxy p = (i.next()).proxy; if(p.compareTo(proxy) == 0) { return true; } } return false; } @Override public NodeProxy get(final int pos) { final IteratorItem item = (IteratorItem) list.get(pos); return item == null ? null : item.proxy; } public NodeProxy get(final DocumentImpl doc, final NodeId nodeId) { final NodeProxy proxy = new NodeProxy(doc, nodeId); for(final Iterator<IteratorItem> i = list.iterator(); i.hasNext(); ) { final NodeProxy p = (i.next()).proxy; if(p.compareTo(proxy) == 0) { return p; } } return null; } @Override public NodeProxy get(final NodeProxy proxy) { for(final Iterator<IteratorItem> i = list.iterator(); i.hasNext(); ) { final NodeProxy p = (i.next()).proxy; if(p.compareTo(proxy) == 0) { return p; } } return null; } @Override public int getLength() { return list.size(); } //TODO : evaluate both semantics (length/item count) @Override public int getItemCount() { return list.size(); } @Override public Node item(final int pos) { final NodeProxy p = ((IteratorItem) list.get(pos)).proxy; return p == null ? null : p.getOwnerDocument().getNode(p); } //TODO : evaluate both semantics (item/itemAt) @Override public Item itemAt(final int pos) { final NodeProxy p = ((IteratorItem) list.get(pos)).proxy; return p == null ? null : p; } @Override public NodeSetIterator iterator() { return new SortedNodeSetIterator(list.iterator()); } @Override public SequenceIterator iterate() throws XPathException { return new SortedNodeSetIterator(list.iterator()); } @Override public SequenceIterator unorderedIterator() throws XPathException { return new SortedNodeSetIterator(list.iterator()); } private static final class SortedNodeSetIterator implements NodeSetIterator, SequenceIterator { private final Iterator<IteratorItem> ii; public SortedNodeSetIterator(final Iterator<IteratorItem> i) { ii = i; } public final boolean hasNext() { return ii.hasNext(); } @Override public final NodeProxy next() { if(!ii.hasNext()) { throw new NoSuchElementException(); } else { return ii.next().proxy; } } @Override public final void remove() { throw new UnsupportedOperationException(); } @Override public final NodeProxy peekNode() { return null; } @Override public final Item nextItem() { if(!ii.hasNext()) { return null; } else { return ii.next().proxy; } } @Override public final void setPosition(final NodeProxy proxy) { throw new UnsupportedOperationException("NodeSetIterator.setPosition() is not supported by SortedNodeSetIterator"); } } private static final class IteratorItem extends OrderedLinkedList.Node { private final NodeProxy proxy; private String value = null; public IteratorItem(final NodeProxy proxy, final PathExpr expr) { this.proxy = proxy; try { final Sequence seq = expr.eval(proxy); final StringBuilder buf = new StringBuilder(); final OrderedLinkedList strings = new OrderedLinkedList(); Item item; for(final SequenceIterator i = seq.iterate(); i.hasNext(); ) { item = i.nextItem(); strings.add(new OrderedLinkedList.SimpleNode(item.getStringValue().toUpperCase())); } for(final Iterator<OrderedLinkedList.SimpleNode> j = strings.iterator(); j.hasNext(); ) { buf.append((j.next()).getData()); } value = buf.toString(); } catch(final XPathException e) { LOG.warn(e.getMessage(), e); //TODO : throw exception ! -pb } } @Override public int compareTo(final OrderedLinkedList.Node other) { final IteratorItem o = (IteratorItem) other; if(value == null) { return o.value == null ? Constants.EQUAL : Constants.SUPERIOR; } else if(o.value == null) { return Constants.INFERIOR; } else { return value.compareTo(o.value); } } @Override public boolean equals(final OrderedLinkedList.Node other) { final IteratorItem o = (IteratorItem) other; return value.equals(o.value); } } @Override public void add(final NodeProxy proxy) { LOG.info("Called SortedNodeSet.add()"); } }