/*
* 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.dom;
import org.exist.numbering.NodeId;
import org.exist.storage.DBBroker;
import org.exist.storage.lock.Lock;
import org.exist.util.ArrayUtils;
import org.exist.util.FastQSort;
import org.exist.util.LockException;
import org.exist.util.hashtable.ObjectHashSet;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.Constants;
import org.exist.xquery.Expression;
import org.exist.xquery.XPathException;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.SequenceIterator;
import org.exist.xquery.value.Type;
import org.w3c.dom.Node;
import java.util.Arrays;
import java.util.Iterator;
/**
* A fast node set implementation, based on arrays to store nodes and documents.
*
* The class uses an array to store all nodes belonging to one document. Another sorted
* array is used to keep track of the document ids. For each document, we maintain an inner
* class, Part, which stores the array of nodes.
*
* Nodes are just appended to the nodes array. No order is guaranteed and calls to
* get/contains may fail although a node is present in the array (get/contains
* do a binary search and thus assume that the set is sorted). Also, duplicates
* are allowed. If you have to ensure that calls to get/contains return valid
* results at any time and no duplicates occur, use class
* {@link org.exist.dom.AVLTreeNodeSet}.
*
* Use this class, if you can either ensure that items are added in order, or
* no calls to contains/get are required during the creation phase. Only after
* a call to one of the iterator methods, the set will get sorted and
* duplicates removed.
*
* @author Wolfgang <wolfgang@exist-db.org>
* @since 0.9.3
*/
public class ExtArrayNodeSet extends AbstractNodeSet implements DocumentSet {
private final static int INITIAL_DOC_SIZE = 64;
private int documentIds[];
private Part parts[];
protected int initalSize = 128;
protected int size = 0;
private int partCount = 0;
private boolean isSorted = false;
private boolean hasOne = false;
protected int lastDoc = -1;
protected Part lastPart = null;
protected NodeProxy lastAdded = null;
private int state = 0;
private MutableDocumentSet cachedDocuments = null;
// used to keep track of the type of added items.
private int itemType = Type.ANY_TYPE;
/**
* Creates a new <code>ExtArrayNodeSet</code> instance.
*
*/
public ExtArrayNodeSet() {
documentIds = new int[INITIAL_DOC_SIZE];
parts = new Part[INITIAL_DOC_SIZE];
Arrays.fill(documentIds, 0);
}
/**
* Creates a new <code>ExtArrayNodeSet</code> instance.
*
* @param initialDocsCount an <code>int</code> value
* @param initialArraySize an <code>int</code> value
*/
public ExtArrayNodeSet(int initialDocsCount, int initialArraySize) {
initalSize = initialArraySize;
if (initialDocsCount == 0)
initialDocsCount = 1;
documentIds = new int[initialDocsCount];
parts = new Part[initialDocsCount];
Arrays.fill(documentIds, 0);
}
/**
* Creates a new <code>ExtArrayNodeSet</code> instance.
*
* @param initialArraySize an <code>int</code> value
*/
public ExtArrayNodeSet(int initialArraySize) {
initalSize = initialArraySize;
documentIds = new int[INITIAL_DOC_SIZE];
parts = new Part[INITIAL_DOC_SIZE];
Arrays.fill(documentIds, 0);
}
/**
* The method <code>getPart</code>
*
* @param doc a <code>DocumentImpl</code> value
* @param create a <code>boolean</code> value
* @param sizeHint an <code>int</code> value
* @return a <code>Part</code> value
*/
protected Part getPart(DocumentImpl doc, boolean create, int sizeHint) {
if (lastPart != null && doc.getDocId() == lastDoc) {
return lastPart;
}
int idx = ArrayUtils.binarySearch(documentIds, doc.getDocId(), partCount);
Part part = null;
if (idx >= 0) {
part = parts[idx];
} else if (create) {
idx = - (idx + 1);
part = new Part(sizeHint);
insertPart(doc.getDocId(), part, idx);
}
return part;
}
/**
* The method <code>reset</code>
*
*/
public void reset() {
for (int i = 0; i < partCount; i++) {
parts[i] = null;
documentIds[i] = 0;
}
size = 0;
partCount = 0;
isSorted = false;
lastPart = null;
lastDoc = -1;
state = 0;
}
private void insertPart(int docId, Part part, int idx) {
if (partCount == parts.length) {
int nsize = parts.length == 0 ? 1 : parts.length * 2;
int ndocs[] = new int[nsize];
System.arraycopy(documentIds, 0, ndocs, 0, documentIds.length);
Arrays.fill(documentIds, -1);
Part nparts[] = new Part[nsize];
System.arraycopy(parts, 0, nparts, 0, parts.length);
documentIds = ndocs;
parts = nparts;
}
if (idx == partCount) {
// insert at the end
documentIds[idx] = docId;
parts[idx] = part;
} else {
// insert at idx
System.arraycopy(documentIds, idx, documentIds, idx + 1, partCount - idx);
System.arraycopy(parts, idx, parts, idx + 1, partCount - idx);
documentIds[idx] = docId;
parts[idx] = part;
}
++partCount;
}
/**
* The method <code>isEmpty</code>
*
* @return a <code>boolean</code> value
*/
public boolean isEmpty() {
return (size == 0);
}
/**
* The method <code>hasOne</code>
*
* @return a <code>boolean</code> value
*/
public boolean hasOne() {
return hasOne;
}
/**
* The method <code>add</code>
*
* @param proxy a <code>NodeProxy</code> value
*/
public void add(NodeProxy proxy) {
if (size > 0) {
if (hasOne) {
if (isSorted) {
hasOne = get(proxy) != null;
} else {
hasOne = lastAdded == null || lastAdded.compareTo(proxy) == 0;
}
}
} else {
hasOne = true;
}
getPart(proxy.getDocument(), true, initalSize).add(proxy);
++size;
isSorted = false;
setHasChanged();
checkItemType(proxy.getType());
lastAdded = proxy;
}
/**
* Add a new node to the set. If a new array of nodes has to be allocated
* for the document, use the sizeHint parameter to determine the size of
* the newly allocated array. This will overwrite the default array size.
*
* If the size hint is correct, no further reallocations will be required.
*/
public void add(NodeProxy proxy, int sizeHint) {
if (size > 0) {
if (hasOne) {
if (isSorted) {
hasOne = get(proxy) == null;
} else {
hasOne = lastAdded == null || lastAdded.compareTo(proxy) == 0;
}
}
} else {
hasOne = true;
}
getPart(proxy.getDocument(), true, sizeHint != Constants.NO_SIZE_HINT ? sizeHint : initalSize).add(
proxy);
++size;
isSorted = false;
setHasChanged();
checkItemType(proxy.getType());
lastAdded = proxy;
}
private void checkItemType(int type) {
if(itemType == Type.NODE || itemType == type) {
return;
}
if(itemType == Type.ANY_TYPE) {
itemType = type;
} else {
itemType = Type.NODE;
}
}
/**
* The method <code>getItemType</code>
*
* @return an <code>int</code> value
*/
public int getItemType() {
return itemType;
}
private void setHasChanged() {
state = (state == Integer.MAX_VALUE ? state = 0 : state + 1);
cachedDocuments = null;
}
/**
* The method <code>getSizeHint</code>
*
* @param doc a <code>DocumentImpl</code> value
* @return an <code>int</code> value
*/
public int getSizeHint(DocumentImpl doc) {
Part part = getPart(doc, false, 0);
return part == null ? Constants.NO_SIZE_HINT : part.length;
}
/*
* (non-Javadoc)
*
* @see org.exist.dom.NodeSet#iterator()
*/
public NodeSetIterator iterator() {
if (!isSorted()) {
sort();
}
return new ExtArrayIterator();
}
/*
* (non-Javadoc)
*
* @see org.exist.xquery.value.Sequence#iterate()
*/
public SequenceIterator iterate() throws XPathException {
sortInDocumentOrder();
return new ExtArrayIterator();
}
/*
* (non-Javadoc)
*
* @see org.exist.dom.AbstractNodeSet#unorderedIterator()
*/
public SequenceIterator unorderedIterator() throws XPathException {
if (!isSorted()) {
sort();
}
return new ExtArrayIterator();
}
public ByDocumentIterator iterateByDocument() {
if (!isSorted()) {
sort();
}
return new ExtDocIterator();
}
/*
* (non-Javadoc)
*
* @see org.exist.dom.NodeSet#contains(org.exist.dom.NodeProxy)
*/
public boolean contains(NodeProxy proxy) {
final Part part = getPart(proxy.getDocument(), false, 0);
return part == null ? false : part.contains(proxy.getNodeId());
}
/*
* (non-Javadoc)
*
* @see org.exist.dom.NodeSet#addAll(org.exist.dom.NodeSet)
*/
public void addAll(NodeSet other) {
if (other.isEmpty())
return;
if (other.hasOne()) {
add((NodeProxy) other.itemAt(0));
} else {
for (Iterator i = other.iterator(); i.hasNext();) {
add((NodeProxy) i.next());
}
}
}
/*
* (non-Javadoc)
*
* @see org.exist.xquery.value.Sequence#getLength()
*/
public int getLength() {
if (!isSorted())
sort(); // sort to remove duplicates
return size;
}
//TODO : evaluate both semantics
public int getItemCount() {
if (!isSorted())
sort(); // sort to remove duplicates
return size;
}
/*
* (non-Javadoc)
*
* @see org.w3c.dom.NodeList#item(int)
*/
public Node item(int pos) {
sortInDocumentOrder();
NodeProxy p = get(pos);
return p == null ? null : p.getNode();
}
/*
* (non-Javadoc)
*
* @see org.exist.dom.NodeSet#get(int)
*/
public NodeProxy get(int pos) {
int count = 0;
Part part;
for (int i = 0; i < partCount; i++) {
part = parts[i];
if (count + part.length > pos)
return part.get(pos - count);
count += part.length;
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.exist.dom.NodeSet#get(org.exist.dom.NodeProxy)
*/
public NodeProxy get(NodeProxy p) {
final Part part = getPart(p.getDocument(), false, 0);
return part == null ? null : part.get(p.getNodeId());
}
public NodeProxy get(DocumentImpl doc, NodeId nodeId) {
sort();
final Part part = getPart(doc, false, 0);
return part == null ? null : part.get(nodeId);
}
/*
* (non-Javadoc)
*
* @see org.exist.xquery.value.Sequence#itemAt(int)
*/
public Item itemAt(int pos) {
sortInDocumentOrder();
return get(pos);
}
/**
* The method <code>getDescendantsInSet</code>
*
* @param al a <code>NodeSet</code> value
* @param childOnly a <code>boolean</code> value
* @param includeSelf a <code>boolean</code> value
* @param mode an <code>int</code> value
* @param contextId an <code>int</code> value
* @return a <code>NodeSet</code> value
*/
public NodeSet getDescendantsInSet(NodeSet al, boolean childOnly, boolean includeSelf, int mode, int contextId,
boolean copyMatches) {
sort();
NodeSet result = new ExtArrayNodeSet();
NodeProxy node;
Part part;
for (Iterator i = al.iterator(); i.hasNext(); ) {
node = (NodeProxy) i.next();
part = getPart(node.getDocument(), false, 0);
if (part != null) {
part.getDescendantsInSet(result, node, childOnly, includeSelf, mode, contextId, copyMatches);
}
}
return result;
}
/**
* The method <code>hasDescendantsInSet</code>
*
* @param doc a <code>DocumentImpl</code> value
* @param ancestorId a <code>NodeId</code> value
* @param includeSelf a <code>boolean</code> value
* @param contextId an <code>int</code> value
* @return a <code>NodeProxy</code> value
*/
public NodeProxy hasDescendantsInSet(DocumentImpl doc, NodeId ancestorId, boolean includeSelf, int contextId) {
sort();
final Part part = getPart(doc, false, 0);
return part == null ? null : part.hasDescendantsInSet(ancestorId, contextId, includeSelf);
}
/**
* The method <code>selectParentChild</code>
*
* @param al a <code>NodeSet</code> value
* @param mode an <code>int</code> value
* @param contextId an <code>int</code> value
* @return a <code>NodeSet</code> value
*/
public NodeSet selectParentChild(NodeSet al, int mode, int contextId) {
sort();
if (al instanceof VirtualNodeSet)
return super.selectParentChild(al, mode, contextId);
return getDescendantsInSet(al, true, false, mode, contextId, true);
}
/**
* The method <code>filterDocuments</code>
*
* @param otherSet an <code>ExtArrayNodeSet</code> value
* @return a <code>NodeSet</code> value
*/
public NodeSet filterDocuments(ExtArrayNodeSet otherSet) {
ExtArrayNodeSet other = otherSet;
ExtArrayNodeSet result = new ExtArrayNodeSet(partCount, other.initalSize);
for (int i = 0; i < other.partCount; i++) {
int idx = ArrayUtils.binarySearch(documentIds, other.documentIds[i], partCount);
if (idx > -1) {
Part part = parts[idx];
int otherIdx = ArrayUtils.binarySearch(result.documentIds, documentIds[idx], result.partCount);
otherIdx = - (otherIdx + 1);
result.insertPart(documentIds[idx], part, otherIdx);
}
}
return result;
}
private boolean isSorted() {
return isSorted;
}
/**
* The method <code>setSorted</code>
*
* @param document a <code>DocumentImpl</code> value
* @param sorted a <code>boolean</code> value
*/
public void setSorted(DocumentImpl document, boolean sorted) {
Part part = getPart(document, false, -1);
if (part != null) {
part.setIsSorted(sorted);
}
}
/**
* Remove all duplicate nodes, but merge their
* contexts.
*/
public void mergeDuplicates() {
sort(true);
}
/**
* The method <code>sort</code>
*
*/
public void sort() {
sort(false);
}
/**
* The method <code>sort</code>
*
* @param mergeContexts a <code>boolean</code> value
*/
public void sort(boolean mergeContexts) {
if (isSorted)
return;
if (hasOne) {
isSorted = true; // shortcut: don't sort if there's just one item
size = parts[0].removeDuplicates(mergeContexts);
return;
}
// long start = System.currentTimeMillis();
Part part;
size = 0;
for (int i = 0; i < partCount; i++) {
part = parts[i];
part.sort();
size += part.removeDuplicates(mergeContexts);
}
isSorted = true;
// System.out.println("sort took " + (System.currentTimeMillis() -
// start) + "ms.");
}
/**
* The method <code>sortInDocumentOrder</code>
*
*/
public final void sortInDocumentOrder() {
sort(false);
}
/*
* (non-Javadoc)
*
* @see org.exist.xquery.value.AbstractSequence#setSelfAsContext()
*/
public void setSelfAsContext(int contextId) throws XPathException {
for (int i = 0; i < partCount; i++) {
parts[i].setSelfAsContext(contextId);
}
}
/* (non-Javadoc)
* @see org.exist.dom.AbstractNodeSet#selectAncestorDescendant(org.exist.dom.NodeSet, int, boolean, boolean)
*/
public NodeSet selectAncestorDescendant(NodeSet al, int mode,
boolean includeSelf, int contextId,
boolean copyMatches) {
sort();
if (al instanceof VirtualNodeSet) {
return super.selectAncestorDescendant(al, mode, includeSelf,
contextId, copyMatches);
}
return getDescendantsInSet(al, false, includeSelf, mode, contextId, copyMatches);
}
/* (non-Javadoc)
* @see org.exist.dom.AbstractNodeSet#selectSiblings(org.exist.dom.NodeSet, int)
*/
public NodeSet selectPrecedingSiblings(NodeSet siblings, int contextId) {
sort();
return super.selectPrecedingSiblings(siblings, contextId);
}
/**
* The method <code>selectFollowingSiblings</code>
*
* @param siblings a <code>NodeSet</code> value
* @param contextId an <code>int</code> value
* @return a <code>NodeSet</code> value
*/
public NodeSet selectFollowingSiblings(NodeSet siblings, int contextId) {
sort();
return super.selectFollowingSiblings(siblings, contextId);
}
/* (non-Javadoc)
* @see org.exist.dom.AbstractNodeSet#selectAncestors(org.exist.dom.NodeSet, boolean, boolean)
*/
public NodeSet selectAncestors(NodeSet al, boolean includeSelf, int contextId) {
sort();
return super.selectAncestors(al, includeSelf, contextId);
}
/**
* The method <code>parentWithChild</code>
*
* @param doc a <code>DocumentImpl</code> value
* @param nodeId a <code>NodeId</code> value
* @param directParent a <code>boolean</code> value
* @param includeSelf a <code>boolean</code> value
* @return a <code>NodeProxy</code> value
*/
public NodeProxy parentWithChild(DocumentImpl doc, NodeId nodeId, boolean directParent, boolean includeSelf) {
sort();
lastPart = getPart(doc, false, initalSize);
return lastPart == null ? null : lastPart.parentWithChild(doc, nodeId, directParent, includeSelf);
}
/**
* The method <code>debugParts</code>
*
* @return a <code>String</code> value
*/
public String debugParts() {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < partCount; i++) {
buf.append(documentIds[i]);
buf.append(' ');
}
return buf.toString();
}
/* (non-Javadoc)
* @see org.exist.dom.NodeSet#getIndexType()
*/
public int getIndexType() {
//Is the index type initialized ?
if (indexType == Type.ANY_TYPE) {
hasTextIndex = false;
hasMixedContent = false;
for (int i = 0; i < partCount; i++) {
parts[i].determineIndexType();
}
}
return indexType;
}
/**
* The method <code>getDocumentSet</code>
*
* @return a <code>DocumentSet</code> value
*/
public DocumentSet getDocumentSet() {
return this;
// if(cachedDocuments != null)
// return cachedDocuments;
// cachedDocuments = new DefaultDocumentSet(partCount);
// sort();
// for (int i = 0; i < partCount; i++) {
// cachedDocuments.add(parts[i].getDocument(), false);
// }
// isSorted = true;
// return cachedDocuments;
}
/**
* The method <code>setDocumentSet</code>
*
* @param docs a <code>DocumentSet</code> value
*/
public void setDocumentSet(DocumentSet docs) {
cachedDocuments = new DefaultDocumentSet();
cachedDocuments.addAll(docs);
}
public boolean equalDocs(DocumentSet other) {
if (this == other)
// we are comparing the same objects
return true;
if (partCount != other.getDocumentCount())
return false;
for (int i = 0; i < partCount; i++) {
if (!other.contains(parts[i].getDocument().getDocId()))
return false;
}
return true;
}
/**
* The method <code>getCollectionIterator</code>
*
* @return an <code>Iterator</code> value
*/
public Iterator getCollectionIterator() {
return new CollectionIterator();
}
// DocumentSet methods
public Iterator getDocumentIterator() {
return new DocumentIterator();
}
public int getDocumentCount() {
return partCount;
}
public DocumentImpl getDocumentAt(int pos) {
return parts[pos].getDocument();
}
public DocumentImpl getDoc(int docId) {
int idx = ArrayUtils.binarySearch(documentIds, docId, partCount);
if (idx > -1)
return parts[idx].getDocument();
else
return null;
}
public XmldbURI[] getNames() {
XmldbURI[] uris = new XmldbURI[partCount];
for (int i = 0; i < partCount; i++) {
uris[i] = parts[i].getDocument().getURI();
}
return uris;
}
public DocumentSet intersection(DocumentSet other) {
DefaultDocumentSet r = new DefaultDocumentSet();
DocumentImpl d;
for (int i = 0; i < partCount; i++) {
d = parts[i].getDocument();
if (other.contains(d.getDocId()))
r.add(d);
}
for (Iterator i = other.getDocumentIterator(); i.hasNext();) {
d = (DocumentImpl) i.next();
if (contains(d.getDocId()) && (!r.contains(d.getDocId())))
r.add(d);
}
return r;
}
public boolean contains(DocumentSet other) {
if (other.getDocumentCount() > partCount)
return false;
DocumentImpl d;
for (Iterator i = other.getDocumentIterator(); i.hasNext();) {
d = (DocumentImpl) i.next();
if (!contains(d.getDocId()))
return false;
}
return true;
}
public boolean contains(int docId) {
return ArrayUtils.binarySearch(documentIds, docId, partCount) > -1;
}
public NodeSet docsToNodeSet() {
NodeSet result = new ExtArrayNodeSet(partCount);
DocumentImpl doc;
for (int i = 0; i < partCount; i++) {
doc = parts[i].getDocument();
if(doc.getResourceType() == DocumentImpl.XML_FILE) { // skip binary resources
result.add(new NodeProxy(doc, NodeId.DOCUMENT_NODE));
}
}
return result;
}
public void lock(DBBroker broker, boolean exclusive, boolean checkExisting) throws LockException {
DocumentImpl d;
Lock dlock;
for (int idx = 0; idx < partCount; idx++) {
d = parts[idx].getDocument();
dlock = d.getUpdateLock();
if (exclusive)
dlock.acquire(Lock.WRITE_LOCK);
else
dlock.acquire(Lock.READ_LOCK);
}
}
public void unlock(boolean exclusive) {
DocumentImpl d;
Lock dlock;
final Thread thread = Thread.currentThread();
for(int idx = 0; idx < partCount; idx++) {
d = parts[idx].getDocument();
dlock = d.getUpdateLock();
if(exclusive)
dlock.release(Lock.WRITE_LOCK);
else if (dlock.isLockedForRead(thread))
dlock.release(Lock.READ_LOCK);
}
}
private class DocumentIterator implements Iterator {
int currentDoc = 0;
public boolean hasNext() {
return currentDoc < partCount;
}
public Object next() {
if (currentDoc == partCount)
return null;
else
return parts[currentDoc++].getDocument();
}
public void remove() {
}
}
/**
* The class <code>CollectionIterator</code>
*
*/
private class CollectionIterator implements Iterator {
Iterator iterator = null;
CollectionIterator() {
if (partCount > 0) {
ObjectHashSet collections = new ObjectHashSet();
Part part;
for (int i = 0; i < partCount; i++) {
part = parts[i];
collections.add(part.getDocument().getCollection());
}
iterator = collections.iterator();
}
}
/**
* The method <code>hasNext</code>
*
* @return a <code>boolean</code> value
*/
public boolean hasNext() {
return iterator != null && iterator.hasNext();
}
/**
* The method <code>next</code>
*
* @return an <code>Object</code> value
*/
public Object next() {
return iterator.next();
}
/**
* The method <code>remove</code>
*
*/
public void remove() {
// not needed
throw new IllegalStateException();
}
}
/*
* (non-Javadoc)
*
* @see org.exist.dom.AbstractNodeSet#hasChanged(int)
*/
public boolean hasChanged(int previousState) {
return state != previousState;
}
/*
* (non-Javadoc)
*
* @see org.exist.dom.AbstractNodeSet#getState()
*/
public int getState() {
return state;
}
public boolean isCacheable() {
return true;
}
/**
* The method <code>toString</code>
*
* @return a <code>String</code> value
*/
public String toString() {
StringBuilder result = new StringBuilder();
result.append("ExtArrayTree#").append(super.toString());
return result.toString();
}
/**
* The class <code>Part</code>
*
*/
private final class Part {
private boolean isSorted = false;
private NodeProxy array[];
private int length = 0;
/**
* Creates a new <code>Part</code> instance.
*
* @param initialSize an <code>int</code> value
*/
Part(int initialSize) {
array = new NodeProxy[initialSize];
}
/**
* The method <code>selectParentChild</code>
*
* @param result a <code>NodeSet</code> value
* @param na a <code>NodeProxy</code> value
* @param ia a <code>NodeSetIterator</code> value
* @param mode an <code>int</code> value
* @param contextId an <code>int</code> value
*/
public void selectParentChild(NodeSet result, NodeProxy na, NodeSetIterator ia, int mode, int contextId) {
if (length == 0)
return;
int pos = 0;
int startPos = 0;
NodeProxy nb = array[pos];
NodeId lastMarked = na.getNodeId();
while (true) {
// first, try to find nodes belonging to the same doc
if (na.getDocument().getDocId() != nb.getDocument().getDocId()) {
break;
}
// same document
NodeId pa = na.getNodeId();
NodeId pb = nb.getNodeId();
int relation = pb.computeRelation(pa);
if (relation != -1) {
if (relation == NodeId.IS_CHILD) {
if(mode == NodeSet.DESCENDANT) {
if (Expression.NO_CONTEXT_ID != contextId) {
nb.addContextNode(contextId, na);
} else {
nb.copyContext(na);
}
result.add(nb);
} else {
if (Expression.NO_CONTEXT_ID != contextId) {
na.addContextNode(contextId, nb);
} else {
na.copyContext(nb);
}
result.add(na);
}
}
if (++pos < length)
nb = array[pos];
else if (ia.hasNext()) {
NodeProxy next = ia.peekNode();
if (next.getNodeId().isDescendantOf(pa)) {
pos = startPos;
nb = array[pos];
na = (NodeProxy) ia.next();
startPos = pos;
} else {
break;
}
} else {
break;
}
} else {
int cmp = pa.compareTo(pb);
if (cmp < 0) {
if (ia.hasNext()) {
NodeProxy next = (NodeProxy) ia.next();
if (next.getNodeId().isDescendantOf(pa)) {
pos = startPos;
nb = array[pos];
} else {
if (!next.getNodeId().isDescendantOf(lastMarked)) {
lastMarked = next.getNodeId();
startPos = pos;
}
}
na = next;
} else {
break;
}
} else {
if (++pos < length) {
nb = array[pos];
} else {
if (ia.hasNext()) {
NodeProxy next = (NodeProxy) ia.next();
if (next.getNodeId().isDescendantOf(pa)) {
pos = startPos;
nb = array[pos];
}
na = next;
}
}
}
}
}
}
/**
* The method <code>add</code>
*
* @param p a <code>NodeProxy</code> value
*/
void add(NodeProxy p) {
// just check if this node has already been added. We only
// check the last entry, which should avoid most of the likely
// duplicates. The remaining duplicates are removed by
// removeDuplicates().
/* ljo's modification, currently breaks the test suite (in-memory vs stored nodes ?) :
NodeId nodeId = p.getNodeId();
if (!NodeId.ROOT_NODE.equals(nodeId)) {
if (length > 0 &&
array[length - 1].getNodeId().equals(nodeId)) {
*/
if (length > 0 && array[length - 1].getNodeId().equals(p.getNodeId())) {
array[length - 1].addMatches(p);
return;
//} ljo's modification
}
if (length == array.length) {
//int newLength = (length * 3)/2 + 1;
final int newLength = length << 1;
NodeProxy temp[] = new NodeProxy[newLength];
System.arraycopy(array, 0, temp, 0, length);
array = temp;
}
array[length++] = p;
}
/**
* The method <code>contains</code>
*
* @param nodeId a <code>NodeId</code> value
* @return a <code>boolean</code> value
*/
boolean contains(NodeId nodeId) {
return get(nodeId) != null;
}
/**
* The method <code>get</code>
*
* @param pos an <code>int</code> value
* @return a <code>NodeProxy</code> value
*/
NodeProxy get(int pos) {
return array[pos];
}
/**
* The method <code>get</code>
*
* @param nodeId a <code>NodeId</code> value
* @return a <code>NodeProxy</code> value
*/
NodeProxy get(NodeId nodeId) {
int low = 0;
int high = length - 1;
int mid, cmp;
NodeProxy p;
while (low <= high) {
mid = (low + high) / 2;
p = array[mid];
cmp = p.getNodeId().compareTo(nodeId);
if (cmp == 0) {
return p;
}
if (cmp > 0) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return null;
}
/**
* The method <code>getDocument</code>
*
* @return a <code>DocumentImpl</code> value
*/
DocumentImpl getDocument() {
if(length == 0) {
return null;
}
return array[0].getDocument();
}
/**
* The method <code>setIsSorted</code>
*
* @param sorted a <code>boolean</code> value
*/
void setIsSorted(boolean sorted) {
this.isSorted = sorted;
}
/**
* The method <code>sort</code>
*
*/
void sort() {
if (isSorted) {
return;
}
FastQSort.sortByNodeId(array, 0, length - 1);
}
/**
* The method <code>sortInDocumentOrder</code>
*
*/
void sortInDocumentOrder() {
sort();
}
/**
* Check if the node identified by its node id has an ancestor
* contained in this node set and return the ancestor found.
*
* If directParent is true, only immediate ancestors (parents) are
* considered. Otherwise the method will call itself recursively for
* all the node's parents.
*
* If includeSelf is true, the method returns also true if the node
* itself is contained in the node set.
* @param doc a <code>DocumentImpl</code> value
* @param nodeId a <code>NodeId</code> value
* @param directParent a <code>boolean</code> value
* @param includeSelf a <code>boolean</code> value
* @return a <code>NodeProxy</code> value
*/
NodeProxy parentWithChild(DocumentImpl doc, NodeId nodeId, boolean directParent, boolean includeSelf) {
NodeProxy temp;
if (includeSelf && (temp = get(nodeId)) != null) {
return temp;
}
nodeId = nodeId.getParentId();
while (nodeId != null) {
if ((temp = get(nodeId)) != null) {
return temp;
} else if (directParent) {
return null;
}
nodeId = nodeId.getParentId();
}
return null;
}
/**
* The method <code>hasDescendantsInSet</code>
*
* @param ancestorId a <code>NodeId</code> value
* @param contextId an <code>int</code> value
* @param includeSelf a <code>boolean</code> value
* @return a <code>NodeProxy</code> value
*/
NodeProxy hasDescendantsInSet(NodeId ancestorId, int contextId, boolean includeSelf) {
// do a binary search to pick some node in the range of valid child
// ids
int low = 0;
int high = length - 1;
int mid = 0;
int cmp;
NodeId id;
while (low <= high) {
mid = (low + high) / 2;
id = array[mid].getNodeId();
if (id.isDescendantOrSelfOf(ancestorId)) {
break; // found a child node, break out.
}
cmp = id.compareTo(ancestorId);
if (cmp > 0) {
high = mid - 1;
} else {
low = mid + 1;
}
}
if (low > high) {
return null; // no node found
}
// find the first child node in the range
while (mid > 0 && array[mid - 1].getNodeId().compareTo(ancestorId) >= 0) {
--mid;
}
NodeProxy ancestor = new NodeProxy(getDocument(), ancestorId, Node.ELEMENT_NODE);
// we need to check if self should be included
boolean foundOne = false;
for (int i = mid; i < length; i++) {
cmp = array[i].getNodeId().computeRelation(ancestorId);
if (cmp > -1) {
boolean add = true;
if (cmp == NodeId.IS_SELF) {
add = includeSelf;
}
if (add) {
if (Expression.NO_CONTEXT_ID != contextId) {
ancestor.deepCopyContext(array[i], contextId);
} else {
ancestor.copyContext(array[i]);
}
ancestor.addMatches(array[i]);
foundOne = true;
}
} else {
break;
}
}
return foundOne ? ancestor : null;
}
/**
* Find all nodes in the current set being children or descendants of
* the given parent node.
*
* @param result the node set to which matching nodes will be appended.
* @param parent the parent node to search for.
* @param childOnly only include child nodes, not descendant nodes
* @param includeSelf include the self:: axis
* @param mode
* @param contextId
*/
NodeSet getDescendantsInSet(NodeSet result, NodeProxy parent, boolean childOnly,
boolean includeSelf, int mode, int contextId, boolean copyMatches) {
NodeProxy p;
NodeId parentId = parent.getNodeId();
// document nodes are treated specially
if (parentId == NodeId.DOCUMENT_NODE) {
for (int i = 0; i < length; i++) {
boolean add;
if (childOnly) {
add = array[i].getNodeId().getTreeLevel() == 1;
} else if (includeSelf) {
add = true;
} else {
add = array[i].getNodeId() != NodeId.DOCUMENT_NODE;
}
if (add) {
switch (mode) {
case NodeSet.DESCENDANT :
if (Expression.NO_CONTEXT_ID != contextId) {
//array[i].addContextNode(contextId, parent);
array[i].deepCopyContext(parent, contextId);
} else {
array[i].copyContext(parent);
}
if (copyMatches)
array[i].addMatches(parent);
result.add(array[i]);
break;
case NodeSet.ANCESTOR :
if (Expression.NO_CONTEXT_ID != contextId) {
//parent.addContextNode(contextId, array[i]);
parent.deepCopyContext(array[i], contextId);
} else {
parent.copyContext(array[i]);
}
if (copyMatches)
parent.addMatches(array[i]);
result.add(parent, 1);
break;
}
}
}
} else {
// do a binary search to pick some node in the range of valid
// child ids
int low = 0;
int high = length - 1;
int mid = 0;
int cmp;
while (low <= high) {
mid = (low + high) / 2;
p = array[mid];
if (p.getNodeId().isDescendantOrSelfOf(parentId)) {
break; // found a child node, break out.
}
cmp = p.getNodeId().compareTo(parentId);
if (cmp > 0) {
high = mid - 1;
} else {
low = mid + 1;
}
}
if (low > high) {
return result; // no node found
}
// find the first child node in the range
while (mid > 0 && array[mid - 1].getNodeId().compareTo(parentId) > -1) {
--mid;
}
// walk through the range of child nodes we found
for (int i = mid; i < length; i++) {
cmp = array[i].getNodeId().computeRelation(parentId);
if (cmp > -1) {
boolean add = true;
if (childOnly) {
add = cmp == NodeId.IS_CHILD;
} else if (cmp == NodeId.IS_SELF) {
add = includeSelf;
}
if (add) {
switch (mode) {
case NodeSet.DESCENDANT :
if (Expression.NO_CONTEXT_ID != contextId) {
//array[i].addContextNode(contextId, parent);
array[i].deepCopyContext(parent, contextId);
} else {
array[i].copyContext(parent);
}
array[i].addMatches(parent);
result.add(array[i]);
break;
case NodeSet.ANCESTOR :
if (Expression.NO_CONTEXT_ID != contextId) {
//parent.addContextNode(contextId, array[i]);
parent.deepCopyContext(array[i], contextId);
} else {
parent.copyContext(array[i]);
}
parent.addMatches(array[i]);
result.add(parent, 1);
break;
}
}
} else {
break;
}
}
}
return result;
}
/**
* Remove all duplicate nodes from this part.
*
* @param mergeContext a <code>boolean</code> value
* @return the new length of the part, after removing all duplicates
*/
int removeDuplicates(boolean mergeContext) {
int j = 0;
for (int i = 1; i < length; i++) {
if (!array[i].getNodeId().equals(array[j].getNodeId())) {
if (i != ++j) {
array[j] = array[i];
}
} else {
if (mergeContext)
array[j].addContext(array[i]);
array[j].addMatches(array[i]);
}
}
length = ++j;
return length;
}
/**
* The method <code>determineIndexType</code>
*
*/
void determineIndexType() {
//Is the index type initialized ?
if (indexType == Type.ANY_TYPE) {
hasTextIndex = true;
hasMixedContent = true;
for (int i = 0; i < length; i++) {
NodeProxy node = array[i];
if (node.getDocument().getCollection().isTempCollection()) {
//Temporary nodes return default values
indexType = Type.ITEM;
hasTextIndex = false;
hasMixedContent = false;
break;
}
int nodeIndexType = node.getIndexType();
//Refine type
//TODO : use common subtype
if (indexType == Type.ANY_TYPE) {
indexType = nodeIndexType;
} else {
//Broaden type
//TODO : use common supertype
if (indexType != nodeIndexType)
indexType = Type.ITEM;
}
if(!node.hasTextIndex()) {
hasTextIndex = false;
}
if(!node.hasMixedContent()) {
hasMixedContent = false;
}
}
}
}
/**
* The method <code>setSelfAsContext</code>
*
* @param contextId an <code>int</code> value
*/
void setSelfAsContext(int contextId) {
for (int i = 0; i < length; i++) {
array[i].addContextNode(contextId, array[i]);
}
}
}
/**
* The class <code>ExtArrayIterator</code>
*
*/
private class ExtArrayIterator implements NodeSetIterator, SequenceIterator {
Part currentPart = null;
int partPos = 0;
int pos = 0;
NodeProxy next = null;
/**
* Creates a new <code>ExtArrayIterator</code> instance.
*
*/
ExtArrayIterator() {
if (partPos < partCount) {
currentPart = parts[partPos];
}
if (currentPart != null && currentPart.length > 0) {
next = currentPart.get(0);
}
}
/**
* The method <code>setPosition</code>
*
* @param proxy a <code>NodeProxy</code> value
*/
public void setPosition(NodeProxy proxy) {
partPos = ArrayUtils.binarySearch(documentIds, proxy.getDocument().getDocId(), partCount);
if (partPos >= 0) {
currentPart = parts[partPos];
int low = 0;
int high = currentPart.length - 1;
int mid;
NodeProxy p;
while (low <= high) {
mid = (low + high) / 2;
p = currentPart.array[mid];
int cmp = p.getNodeId().compareTo(proxy.getNodeId());
if (cmp == 0) {
pos = mid;
next = p;
return;
}
if (cmp > 0) {
high = mid - 1;
} else {
low = mid + 1;
}
}
}
next = null;
}
/*
* (non-Javadoc)
*
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
return next != null;
}
/*
* (non-Javadoc)
*
* @see java.util.Iterator#next()
*/
public Object next() {
if (next == null)
return null;
NodeProxy n = next;
next = null;
if (++pos == currentPart.length) {
if (++partPos < partCount) {
currentPart = parts[partPos];
if (currentPart != null && currentPart.length > 0) {
next = currentPart.get(0);
pos = 0;
}
}
} else
next = currentPart.get(pos);
return n;
}
public NodeProxy peekNode() {
return next;
}
/*
* (non-Javadoc)
*
* @see org.exist.xquery.value.SequenceIterator#nextItem()
*/
public Item nextItem() {
return (Item) next();
}
/*
* (non-Javadoc)
*
* @see java.util.Iterator#remove()
*/
public void remove() {
}
}
/**
* The class <code>ExtDocIterator</code>
*
*/
private class ExtDocIterator implements ByDocumentIterator {
Part currentPart = null;
int pos = 0;
NodeProxy next = null;
/**
* Creates a new <code>ExtDocIterator</code> instance.
*
*/
public ExtDocIterator() {
if (partCount > 0) {
currentPart = parts[0];
}
if (currentPart != null && currentPart.length > 0) {
next = currentPart.get(0);
}
}
/**
* The method <code>nextDocument</code>
*
* @param document a <code>DocumentImpl</code> value
*/
public void nextDocument(DocumentImpl document) {
currentPart = getPart(document, false, -1);
pos = 0;
if (currentPart != null && currentPart.length > 0) {
next = currentPart.get(0);
} else {
next = null;
}
}
/**
* The method <code>hasNextNode</code>
*
* @return a <code>boolean</code> value
*/
public boolean hasNextNode() {
return next != null;
}
/**
* The method <code>nextNode</code>
*
* @return a <code>NodeProxy</code> value
*/
public NodeProxy nextNode() {
if (next == null) {
return null;
}
NodeProxy n = next;
next = null;
if (++pos < currentPart.length) {
next = currentPart.get(pos);
}
return n;
}
/**
* The method <code>peekNode</code>
*
* @return a <code>NodeProxy</code> value
*/
public NodeProxy peekNode() {
return next;
}
/**
* The method <code>setPosition</code>
*
* @param node a <code>NodeProxy</code> value
*/
public void setPosition(NodeProxy node) {
currentPart = getPart(node.getDocument(), false, -1);
int low = 0;
int high = currentPart.length - 1;
int mid;
NodeProxy p;
while (low <= high) {
mid = (low + high) / 2;
p = currentPart.array[mid];
int cmp = p.getNodeId().compareTo(node.getNodeId());
if (cmp == 0) {
pos = mid;
next = p;
return;
}
if (cmp > 0) {
high = mid - 1;
} else {
low = mid + 1;
}
}
next = null;
}
}
}