/* * eXist Open Source Native XML Database * Copyright (C) 2001-2007 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.xquery.value; import org.exist.dom.AVLTreeNodeSet; import org.exist.dom.NodeProxy; import org.exist.dom.NodeSet; import org.exist.memtree.DocumentImpl; import org.exist.memtree.NodeImpl; import org.exist.numbering.NodeId; import org.exist.util.FastQSort; import org.exist.xquery.Constants; import org.exist.xquery.OrderSpec; import org.exist.xquery.XPathException; import org.exist.xquery.util.ExpressionDumper; import org.w3c.dom.Node; /** * A sequence that sorts its entries in the order specified by the order specs of * an "order by" clause. Used by {@link org.exist.xquery.ForExpr}. * * Contrary to class {@link org.exist.xquery.value.PreorderedValueSequence}, * all order expressions are evaluated once for each item in the sequence * <b>while</b> items are added. * * @author wolf */ public class OrderedValueSequence extends AbstractSequence { private OrderSpec orderSpecs[]; private Entry[] items = null; private int count = 0; private int state = 0; // used to keep track of the type of added items. private int itemType = Type.ANY_TYPE; public OrderedValueSequence(OrderSpec orderSpecs[], int size) { this.orderSpecs = orderSpecs; if (size == 0) size = 1; this.items = new Entry[size]; } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#iterate() */ public SequenceIterator iterate() throws XPathException { return new OrderedValueSequenceIterator(); } /* (non-Javadoc) * @see org.exist.xquery.value.AbstractSequence#unorderedIterator() */ public SequenceIterator unorderedIterator() throws XPathException { return new OrderedValueSequenceIterator(); } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#getLength() */ public int getItemCount() { return (items == null) ? 0 : count; } public boolean isEmpty() { return isEmpty; } public boolean hasOne() { return hasOne; } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#add(org.exist.xquery.value.Item) */ public void add(Item item) throws XPathException { if (hasOne) hasOne = false; if (isEmpty) hasOne = true; isEmpty = false; if(count == 0 && items.length == 1) { items = new Entry[2]; } else if (count == items.length) { Entry newItems[] = new Entry[count * 2]; System.arraycopy(items, 0, newItems, 0, count); items = newItems; } items[count++] = new Entry(item); checkItemType(item.getType()); setHasChanged(); } /* (non-Javadoc) * @see org.exist.xquery.value.AbstractSequence#addAll(org.exist.xquery.value.Sequence) */ public void addAll(Sequence other) throws XPathException { if(other.hasOne()) add(other.itemAt(0)); else if(!other.isEmpty()) { for(SequenceIterator i = other.iterate(); i.hasNext(); ) { Item next = i.nextItem(); if(next != null) add(next); } } } public void sort() { FastQSort.sort(items, 0, count - 1); } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#itemAt(int) */ public Item itemAt(int pos) { if(items != null && pos > -1 && pos < count) return items[pos].item; else return null; } private void checkItemType(int type) { if(itemType == Type.NODE || itemType == type) return; if(itemType == Type.ANY_TYPE) itemType = type; else itemType = Type.NODE; } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#getItemType() */ public int getItemType() { return itemType; } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#toNodeSet() */ public NodeSet toNodeSet() throws XPathException { //return early if (isEmpty()) return NodeSet.EMPTY_SET; // for this method to work, all items have to be nodes if(itemType != Type.ANY_TYPE && Type.subTypeOf(itemType, Type.NODE)) { //Was ExtArrayNodeset() which orders the nodes in document order //The order seems to change between different invocations !!! NodeSet set = new AVLTreeNodeSet(); //We can't make it from an ExtArrayNodeSet (probably because it is sorted ?) //NodeSet set = new ArraySet(100); for (int i = 0; i < items.length; i++) { //TODO : investigate why we could have null here if (items[i] != null) { NodeValue v = (NodeValue)items[i].item; if(v.getImplementationType() != NodeValue.PERSISTENT_NODE) { // found an in-memory document org.exist.memtree.DocumentImpl doc = ((NodeImpl)v).getDocument(); if (doc==null) { continue; } // make this document persistent: doc.makePersistent() // returns a map of all root node ids mapped to the corresponding // persistent node. We scan the current sequence and replace all // in-memory nodes with their new persistent node objects. DocumentImpl expandedDoc = doc.expandRefs(null); org.exist.dom.DocumentImpl newDoc = expandedDoc.makePersistent(); if (newDoc != null) { NodeId rootId = newDoc.getBrokerPool().getNodeFactory().createInstance(); for (int j = i; j < count; j++) { v = (NodeValue) items[j].item; if(v.getImplementationType() != NodeValue.PERSISTENT_NODE) { NodeImpl node = (NodeImpl) v; if (node.getDocument() == doc) { node = expandedDoc.getNode(node.getNodeNumber()); NodeId nodeId = node.getNodeId(); if (nodeId == null) throw new XPathException("Internal error: nodeId == null"); if (node.getNodeType() == Node.DOCUMENT_NODE) nodeId = rootId; else nodeId = rootId.append(nodeId); NodeProxy p = new NodeProxy(newDoc, nodeId, node.getNodeType()); if (p != null) { // replace the node by the NodeProxy items[j].item = p; } } } } } set.add((NodeProxy) items[i].item); } else { set.add((NodeProxy)v); } } } return set; } else throw new XPathException("Type error: the sequence cannot be converted into" + " a node set. Item type is " + Type.getTypeName(itemType)); } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#isPersistentSet() */ public boolean isPersistentSet() { if(count == 0) return true; if(itemType != Type.ANY_TYPE && Type.subTypeOf(itemType, Type.NODE)) { NodeValue v; for (int i = 0; i < count; i++) { v = (NodeValue)items[i].item; if(v.getImplementationType() != NodeValue.PERSISTENT_NODE) return false; } return true; } return false; } public MemoryNodeSet toMemNodeSet() throws XPathException { if(count == 0) return MemoryNodeSet.EMPTY; if(itemType == Type.ANY_TYPE || !Type.subTypeOf(itemType, Type.NODE)) { throw new XPathException("Type error: the sequence cannot be converted into" + " a node set. Item type is " + Type.getTypeName(itemType)); } NodeValue v; for (int i = 0; i < count; i++) { v = (NodeValue)items[i].item; if(v.getImplementationType() == NodeValue.PERSISTENT_NODE) return null; } return new ValueSequence(this); } /* (non-Javadoc) * @see org.exist.xquery.value.Sequence#removeDuplicates() */ public void removeDuplicates() { // TODO: is this ever relevant? } private void setHasChanged() { state = (state == Integer.MAX_VALUE ? state = 0 : state + 1); } public int getState() { return state; } public boolean hasChanged(int previousState) { return state != previousState; } public boolean isCacheable() { return true; } private class Entry implements Comparable { Item item; AtomicValue values[]; public Entry(Item item) throws XPathException { this.item = item; values = new AtomicValue[orderSpecs.length]; for(int i = 0; i < orderSpecs.length; i++) { Sequence seq = orderSpecs[i].getSortExpression().eval(null); values[i] = AtomicValue.EMPTY_VALUE; if(seq.hasOne()) { values[i] = seq.itemAt(0).atomize(); } else if(seq.hasMany()) throw new XPathException("expected a single value for order expression " + ExpressionDumper.dump(orderSpecs[i].getSortExpression()) + " ; found: " + seq.getItemCount()); } } /* (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(Object o) { Entry other = (Entry)o; int cmp = 0; AtomicValue a, b; for(int i = 0; i < values.length; i++) { try { a = values[i]; b = other.values[i]; boolean aIsEmpty = (a.isEmpty() || (Type.subTypeOf(a.getType(), Type.NUMBER) && ((NumericValue) a).isNaN())); boolean bIsEmpty = (b.isEmpty() || (Type.subTypeOf(b.getType(), Type.NUMBER) && ((NumericValue) b).isNaN())); if (aIsEmpty) { if (bIsEmpty) // both values are empty return Constants.EQUAL; else if ((orderSpecs[i].getModifiers() & OrderSpec.EMPTY_LEAST) != 0) cmp = Constants.INFERIOR; else cmp = Constants.SUPERIOR; } else if (bIsEmpty) { // we don't need to check for equality since we know a is not empty if ((orderSpecs[i].getModifiers() & OrderSpec.EMPTY_LEAST) != 0) cmp = Constants.SUPERIOR; else cmp = Constants.INFERIOR; } else if (a == AtomicValue.EMPTY_VALUE && b != AtomicValue.EMPTY_VALUE) { if((orderSpecs[i].getModifiers() & OrderSpec.EMPTY_LEAST) != 0) cmp = Constants.INFERIOR; else cmp = Constants.SUPERIOR; } else if (b == AtomicValue.EMPTY_VALUE && a != AtomicValue.EMPTY_VALUE) { if((orderSpecs[i].getModifiers() & OrderSpec.EMPTY_LEAST) != 0) cmp = Constants.SUPERIOR; else cmp = Constants.INFERIOR; } else cmp = a.compareTo(orderSpecs[i].getCollator(), b); if((orderSpecs[i].getModifiers() & OrderSpec.DESCENDING_ORDER) != 0) cmp = cmp * -1; if(cmp != Constants.EQUAL) break; } catch (XPathException e) { } } return cmp; } } private class OrderedValueSequenceIterator implements SequenceIterator { int pos = 0; /* (non-Javadoc) * @see org.exist.xquery.value.SequenceIterator#hasNext() */ public boolean hasNext() { return pos < count; } /* (non-Javadoc) * @see org.exist.xquery.value.SequenceIterator#nextItem() */ public Item nextItem() { if(pos < count) { return items[pos++].item; } return null; } } }