/* * eXist Open Source Native XML Database * Copyright (C) 2001-2015 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.exist.dom.persistent; import org.exist.EXistException; import org.exist.collections.Collection; import org.exist.dom.QName; import org.exist.dom.memtree.DocumentBuilderReceiver; import org.exist.numbering.NodeId; import org.exist.stax.IEmbeddedXMLStreamReader; import org.exist.storage.DBBroker; import org.exist.storage.RangeIndexSpec; import org.exist.storage.StorageAddress; import org.exist.storage.lock.Lock; import org.exist.storage.lock.Lock.LockMode; import org.exist.storage.serializers.Serializer; import org.exist.util.LockException; import org.exist.xmldb.XmldbURI; import org.exist.xquery.Cardinality; import org.exist.xquery.Constants; import org.exist.xquery.Expression; import org.exist.xquery.NodeTest; import org.exist.xquery.XPathException; import org.exist.xquery.XQueryContext; import org.exist.xquery.value.*; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.ext.LexicalHandler; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import java.io.IOException; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Properties; /** * Placeholder class for DOM nodes. * <p/> * NodeProxy is an internal proxy class, acting as a placeholder for all types of persistent XML nodes * during query processing. NodeProxy just stores the node's unique id and the document it belongs to. * Query processing deals with these proxys most of the time. Using a NodeProxy is much cheaper * than loading the actual node from the database. The real DOM node is only loaded, * if further information is required for the evaluation of an XPath expression. To obtain * the real node for a proxy, simply call {@link #getNode()}. * * All sets of type NodeSet operate on NodeProxys. A node set is a special type of * sequence, so NodeProxy does also implement {@link org.exist.xquery.value.Item} and * can thus be an item in a sequence. Since, according to XPath 2, a single node is also * a sequence, NodeProxy does itself extend NodeSet. It thus represents a node set containing * just one, single node. * * @author Wolfgang Meier <wolfgang@exist-db.org> */ public class NodeProxy implements NodeSet, NodeValue, NodeHandle, DocumentSet, Comparable<Object> { public static final short UNKNOWN_NODE_TYPE = -1; public static final int UNKNOWN_NODE_LEVEL = -1; /** * The owner document of this node. */ private DocumentImpl doc = null; private NodeId nodeId; /** * The internal storage address of this node in the * dom.dbx file, if known. * * @link #UNKNOWN_NODE_ADDRESS */ private long internalAddress = StoredNode.UNKNOWN_NODE_IMPL_ADDRESS; /** * The type of this node (as defined by DOM), if known. * * @link #UNKNOWN_NODE_TYPE */ private short nodeType = UNKNOWN_NODE_TYPE; /** * The first {@link Match} object associated with this node. * Match objects are used to track hits throughout query processing. * <p/> * Matches are stored as a linked list. */ private Match match = null; private ContextItem context = null; private QName qname = null; /** * Creates a new <code>NodeProxy</code> instance. * * @param doc a <code>DocumentImpl</code> value * @param nodeId a <code>NodeId</code> value */ public NodeProxy(final DocumentImpl doc, final NodeId nodeId) { this(doc, nodeId, UNKNOWN_NODE_TYPE, StoredNode.UNKNOWN_NODE_IMPL_ADDRESS); } /** * Creates a new <code>NodeProxy</code> instance. * * @param doc a <code>DocumentImpl</code> value * @param nodeId a <code>NodeId</code> value * @param address a <code>long</code> value */ public NodeProxy(final DocumentImpl doc, final NodeId nodeId, final long address) { this(doc, nodeId, UNKNOWN_NODE_TYPE, address); } /** * Creates a new <code>NodeProxy</code> instance. * * @param doc a <code>DocumentImpl</code> value * @param nodeId a <code>NodeId</code> value * @param nodeType a <code>short</code> value */ public NodeProxy(final DocumentImpl doc, final NodeId nodeId, final short nodeType) { this(doc, nodeId, nodeType, StoredNode.UNKNOWN_NODE_IMPL_ADDRESS); } /** * Creates a new <code>NodeProxy</code> instance. * * @param doc a <code>DocumentImpl</code> value * @param nodeId a <code>NodeId</code> value * @param nodeType a <code>short</code> value * @param address a <code>long</code> value */ public NodeProxy(final DocumentImpl doc, final NodeId nodeId, final short nodeType, final long address) { this.doc = doc; this.nodeType = nodeType; this.internalAddress = address; this.nodeId = nodeId; } public void update(final ElementImpl element) { this.doc = element.getOwnerDocument(); this.nodeType = UNKNOWN_NODE_TYPE; this.internalAddress = StoredNode.UNKNOWN_NODE_IMPL_ADDRESS; this.nodeId = element.getNodeId(); match = null; context = null; } /** * Creates a new <code>NodeProxy</code> instance. * * @param n a <code>NodeHandle</code> value */ public NodeProxy(final NodeHandle n) { this(n.getOwnerDocument(), n.getNodeId(), n.getNodeType(), n.getInternalAddress()); if(n instanceof NodeProxy) { this.match = ((NodeProxy) n).match; this.context = ((NodeProxy) n).context; } } /** * create a proxy to a document node * * @param doc a <code>DocumentImpl</code> value */ public NodeProxy(final DocumentImpl doc) { this(doc, NodeId.DOCUMENT_NODE, Node.DOCUMENT_NODE, StoredNode.UNKNOWN_NODE_IMPL_ADDRESS); } @Override public void setNodeId(final NodeId id) { this.nodeId = id; } @Override public NodeId getNodeId() { return nodeId; } @Override public QName getQName() { if (qname == null) { getNode(); } return qname; } public void setQName(QName qname) { this.qname = qname; } @Override public int getImplementationType() { return NodeValue.PERSISTENT_NODE; } @Override public NodeSet copy() { // return this, because there's no other node in the set return this; } @Override public Sequence tail() throws XPathException { return Sequence.EMPTY_SEQUENCE; } /** * The method <code>compareTo</code> * * @param other an <code>Object</code> value * @return an <code>int</code> value */ @Override public int compareTo(final Object other) { if(!(other instanceof NodeProxy)) { //We are always superior... return Constants.SUPERIOR; } else { return compareTo((NodeProxy) other); } } /** * Ordering first according to document ID; then if equal * according to node gid. * * @param other a <code>NodeProxy</code> value * @return an <code>int</code> value */ public int compareTo(final NodeProxy other) { final int diff = doc.getDocId() - other.doc.getDocId(); if(diff != Constants.EQUAL) { return diff; } else { return nodeId.compareTo(other.nodeId); } } /** * The method <code>equals</code> * * @param other an <code>Object</code> value * @return a <code>boolean</code> value */ @Override public boolean equals(final Object other) { if(!(other instanceof NodeProxy)) { return false; } final NodeProxy otherNode = (NodeProxy) other; if(otherNode.doc.getDocId() != doc.getDocId()) { return false; } return otherNode.nodeId.equals(nodeId); } @Override public boolean equals(final NodeValue other) throws XPathException { if(other.getImplementationType() != NodeValue.PERSISTENT_NODE) { throw new XPathException("Cannot compare persistent node with in-memory node"); } final NodeProxy otherNode = (NodeProxy) other; if(otherNode.doc.getDocId() != doc.getDocId()) { return false; } return otherNode.nodeId.equals(nodeId); } @Override public boolean before(final NodeValue other, final boolean isPreceding) throws XPathException { if(other.getImplementationType() != NodeValue.PERSISTENT_NODE) { throw new XPathException("Cannot compare persistent node with in-memory node"); } final NodeProxy otherNode = (NodeProxy) other; if(doc.getDocId() != otherNode.doc.getDocId()) { //Totally arbitrary return doc.getDocId() < otherNode.doc.getDocId(); } return nodeId.before(otherNode.nodeId, isPreceding); } @Override public boolean after(final NodeValue other, final boolean isFollowing) throws XPathException { if(other.getImplementationType() != NodeValue.PERSISTENT_NODE) { throw new XPathException("Cannot compare persistent node with in-memory node"); } final NodeProxy otherNode = (NodeProxy) other; if(doc.getDocId() != otherNode.doc.getDocId()) { //Totally arbitrary return doc.getDocId() > otherNode.doc.getDocId(); } return nodeId.after(otherNode.nodeId, isFollowing); } @Override public DocumentImpl getOwnerDocument() { return doc; } /** * The method <code>isDocument</code> * * @return a <code>boolean</code> value */ public boolean isDocument() { return nodeType == Node.DOCUMENT_NODE; } /** * Gets the node from the broker, i.e. fom the underlying file system * Call this method <string>only</strong> when necessary * @see org.exist.xquery.value.NodeValue#getNode() */ @Override public Node getNode() { if(isDocument()) { return doc; } else { final NodeImpl realNode = (NodeImpl) doc.getNode(this); if(realNode != null) { this.nodeType = realNode.getNodeType(); this.qname = realNode.getQName(); } return realNode; } } @Override public short getNodeType() { return nodeType; } /** * Sets the nodeType. * * @param nodeType The nodeType to set */ public void setNodeType(final short nodeType) { this.nodeType = nodeType; } @Override public long getInternalAddress() { return internalAddress; } @Override public void setInternalAddress(final long internalAddress) { this.internalAddress = internalAddress; } public void setIndexType(int type) { this.internalAddress = StorageAddress.setIndexType(internalAddress, (short) type); } @Override public int getIndexType() { if(internalAddress == -1) { return Type.ITEM; } return RangeIndexSpec.indexTypeToXPath(StorageAddress.indexTypeFromPointer(internalAddress)); } @Override public void setTrackMatches(boolean track) { } @Override public boolean getTrackMatches() { return true; } public Match getMatches() { return match; } public void setMatches(final Match match) { this.match = match; } public void addMatch(final Match m) { if(match == null) { match = m; match.nextMatch = null; return; } Match next = match; while(next != null) { if(next.matchEquals(m)) { next.mergeOffsets(m); return; } if(next.nextMatch == null) { next.nextMatch = m; break; } next = next.nextMatch; } } public void addMatches(final NodeProxy p) { if(p == this) { return; } Match m = p.getMatches(); if(Match.matchListEquals(m, this.match)) { return; } while(m != null) { addMatch(m.newCopy()); m = m.nextMatch; } } /** * Add a node to the list of context nodes for this node. * * NodeProxy internally stores the context nodes of the XPath context, for which * this node has been selected during a previous processing step. * * Since eXist tries to process many expressions in one, single processing step, * the context information is required to resolve predicate expressions. For * example, for an expression like //SCENE[SPEECH/SPEAKER='HAMLET'], * we have to remember the SCENE nodes for which the equality expression * in the predicate was true. Thus, when evaluating the step SCENE[SPEECH], the * SCENE nodes become context items of the SPEECH nodes and this context * information is preserved through all following steps. * * To process the predicate expression, {@link org.exist.xquery.Predicate} will take the * context nodes returned by the filter expression and compare them to its context * node set. */ @Override public void addContextNode(final int contextId, final NodeValue node) { if(node.getImplementationType() != NodeValue.PERSISTENT_NODE) { return; } final NodeProxy contextNode = (NodeProxy) node; if(context == null) { context = new ContextItem(contextId, contextNode); return; } ContextItem next = context; while(next != null) { if(contextId == next.getContextId() && next.getNode().getNodeId().equals(contextNode.getNodeId())) { // Ignore duplicate context nodes break; } if(next.getNextDirect() == null) { if(next == context) { // context items should not be shared between proxies, // but for performance reason, if there's only a single // context item, it will be shared. we thus have to create // a copy before appending a new item. next = new ContextItem(next.getContextId(), next.getNode()); context = next; } next.setNextContextItem(new ContextItem(contextId, contextNode)); break; } next = next.getNextDirect(); } } /** * Add all context nodes from the other NodeProxy to the * context of this NodeProxy. * * @param other */ public void addContext(final NodeProxy other) { ContextItem next = other.context; while(next != null) { addContextNode(next.getContextId(), next.getNode()); next = next.getNextDirect(); } } public void copyContext(final NodeProxy node) { deepCopyContext(node); } /** * Copy the context items from the given node into this node. * Context items are used to keep track of context nodes inside predicates. * * @param node a <code>NodeProxy</code> value */ public void deepCopyContext(final NodeProxy node) { context = null; if(node.context == null) { return; } if(node.context.getNextDirect() == null) { // if there's a single context item, we just // copy a reference to it. addContextNode will take // care of this and create a copy before appending // a new item context = node.context; } else { ContextItem next = node.context; ContextItem newContext = new ContextItem(next.getContextId(), next.getNode()); context = newContext; next = next.getNextDirect(); while(next != null) { newContext.setNextContextItem(new ContextItem(next.getContextId(), next.getNode())); newContext = newContext.getNextDirect(); next = next.getNextDirect(); } } } public void deepCopyContext(final NodeProxy node, final int addContextId) { if(context == null) { deepCopyContext(node); } addContextNode(addContextId, node); } /** * The method <code>clearContext</code> * * @param contextId an <code>int</code> value */ @Override public void clearContext(final int contextId) { if(contextId == Expression.IGNORE_CONTEXT) { context = null; return; } ContextItem newContext = null; ContextItem last = null; ContextItem next = context; while(next != null) { if(next.getContextId() != contextId) { if(newContext == null) { newContext = next; } else { last.setNextContextItem(next); } last = next; last.setNextContextItem(null); } next = next.getNextDirect(); } this.context = newContext; } public ContextItem getContext() { return context; } public String debugContext() { final StringBuilder buf = new StringBuilder(); buf.append("Context for ").append(nodeId).append(" [ ").append(toString()).append("] : "); ContextItem next = context; while(next != null) { buf.append('['); buf.append(next.getNode().getNodeId()); buf.append(':'); buf.append(next.getContextId()); buf.append("] "); next = next.getNextDirect(); } return buf.toString(); } // methods of interface Item @Override public int getType() { return nodeType2XQuery(nodeType); } public static int nodeType2XQuery(final short nodeType) { switch(nodeType) { case Node.ELEMENT_NODE: //TODO : return Type.DOCUMENT for some in-memory nodes : //http://sourceforge.net/tracker/index.php?func=detail&aid=1730690&group_id=17691&atid=117691 //Ideally compute this when proxy is constructed return Type.ELEMENT; case Node.ATTRIBUTE_NODE: return Type.ATTRIBUTE; case Node.TEXT_NODE: return Type.TEXT; case Node.PROCESSING_INSTRUCTION_NODE: return Type.PROCESSING_INSTRUCTION; case Node.COMMENT_NODE: return Type.COMMENT; case Node.DOCUMENT_NODE: return Type.DOCUMENT; //(yet) unknown type : return generic default: return Type.NODE; } } @Override public boolean isPersistentSet() { return true; } @Override public void nodeMoved(final NodeId oldNodeId, final NodeHandle newNode) { if(nodeId.equals(oldNodeId)) { // update myself nodeId = newNode.getNodeId(); internalAddress = newNode.getInternalAddress(); } } @Override public Sequence toSequence() { return this; } public String getNodeValue() { try(final DBBroker broker = doc.getBrokerPool().getBroker()) { if(isDocument()) { final Element e = doc.getDocumentElement(); if(e instanceof NodeProxy) { return broker.getNodeValue(((StoredNode) e).extract(), false); } else if(e != null) { return broker.getNodeValue((ElementImpl) e, false); } else // probably a binary resource { return ""; } } else { return broker.getNodeValue(this.asStoredNode(), false); } } catch(final EXistException e) { //TODO : raise an exception here ! -pb } return ""; } public String getNodeValueSeparated() { try(final DBBroker broker = doc.getBrokerPool().getBroker()) { return broker.getNodeValue(asStoredNode(), true); } catch(final EXistException e) { //TODO : raise an exception here ! } return ""; } //TODO this should be improved. Consider an interface that contains just the // getters from INodeHandle and persistent.NodeHandle public StoredNode asStoredNode() { return new StoredNode( this.getNodeType(), this.getNodeId(), this.getOwnerDocument(), this.getInternalAddress()) { }; } @Override public String getStringValue() { return getNodeValue(); } @Override public AtomicValue convertTo(final int requiredType) throws XPathException { return UntypedAtomicValue.convertTo(getNodeValue(), requiredType); } @Override public AtomicValue atomize() throws XPathException { return new UntypedAtomicValue(getNodeValue()); } @Override public void toSAX(final DBBroker broker, final ContentHandler handler, final Properties properties) throws SAXException { final Serializer serializer = broker.getSerializer(); serializer.reset(); serializer.setProperty(Serializer.GENERATE_DOC_EVENTS, "false"); if(properties != null) { serializer.setProperties(properties); } if(handler instanceof LexicalHandler) { serializer.setSAXHandlers(handler, (LexicalHandler) handler); } else { serializer.setSAXHandlers(handler, null); } serializer.toSAX(this); } @Override public void copyTo(final DBBroker broker, final DocumentBuilderReceiver receiver) throws SAXException { NodeImpl node = null; if(nodeType < 0) { node = (NodeImpl) getNode(); } if(nodeType == Node.ATTRIBUTE_NODE) { final AttrImpl attr = (node == null ? (AttrImpl) getNode() : (AttrImpl) node); receiver.attribute(attr.getQName(), attr.getValue()); } else { receiver.addReferenceNode(this); } } @Override public int conversionPreference(final Class javaClass) { if(javaClass.isAssignableFrom(NodeProxy.class)) { return 0; } else if(javaClass.isAssignableFrom(Node.class)) { return 1; } else if(javaClass == String.class || javaClass == CharSequence.class) { return 2; } else if(javaClass == Character.class || javaClass == char.class) { return 2; } else if(javaClass == Double.class || javaClass == double.class) { return 10; } else if(javaClass == Float.class || javaClass == float.class) { return 11; } else if(javaClass == Long.class || javaClass == long.class) { return 12; } else if(javaClass == Integer.class || javaClass == int.class) { return 13; } else if(javaClass == Short.class || javaClass == short.class) { return 14; } else if(javaClass == Byte.class || javaClass == byte.class) { return 15; } else if(javaClass == Boolean.class || javaClass == boolean.class) { return 16; } else if(javaClass == Object.class) { return 20; } else { return Integer.MAX_VALUE; } } @Override public <T> T toJavaObject(final Class<T> target) throws XPathException { if(target.isAssignableFrom(NodeProxy.class)) { return (T) this; } else if(target.isAssignableFrom(Node.class) || target == Object.class) { return (T) getNode(); } else { final StringValue v = new StringValue(getStringValue()); return v.toJavaObject(target); } } /* * Methods of interface Sequence: */ @Override public int getItemType() { return getType(); } @Override public int getCardinality() { return Cardinality.EXACTLY_ONE; } @Override public boolean isCached() { return false; } @Override public void setIsCached(final boolean cached) { //TODO : return something useful ? -pb } @Override public NodeSet toNodeSet() throws XPathException { return this; } @Override public MemoryNodeSet toMemNodeSet() throws XPathException { return null; } @Override public boolean effectiveBooleanValue() throws XPathException { return true; } @Override public void removeDuplicates() { // single node: no duplicates } @Override public void setSelfAsContext(final int contextId) { addContextNode(contextId, this); } /* -----------------------------------------------* * Methods of class NodeSet * -----------------------------------------------*/ @Override public NodeSetIterator iterator() { return new SingleNodeIterator(this); } @Override public SequenceIterator iterate() throws XPathException { return new SingleNodeIterator(this); } @Override public SequenceIterator unorderedIterator() { return new SingleNodeIterator(this); } @Override public boolean contains(final NodeProxy proxy) { if(doc.getDocId() != proxy.doc.getDocId()) { return false; } else { return nodeId.equals(proxy.getNodeId()); } } @Override public void addAll(final NodeSet other) { throw new UnsupportedOperationException(); } @Override public boolean isEmpty() { return false; } @Override public boolean hasOne() { return true; } @Override public boolean hasMany() { return false; } @Override public void add(final NodeProxy proxy) { throw new UnsupportedOperationException(); } @Override public void add(final Item item) throws XPathException { throw new UnsupportedOperationException(); } @Override public void add(final NodeProxy proxy, final int sizeHint) { throw new UnsupportedOperationException(); } @Override public void addAll(final Sequence other) throws XPathException { throw new UnsupportedOperationException(); } @Override public int getLength() { //TODO : how to delegate to the real node implementation's getLength() ? return 1; } //TODO : evaluate both semantics @Override public int getItemCount() { return 1; } @Override public Node item(final int pos) { return pos > 0 ? null : getNode(); } @Override public Item itemAt(final int pos) { return pos > 0 ? null : this; } @Override public NodeProxy get(final int pos) { return pos > 0 ? null : this; } @Override public NodeProxy get(final NodeProxy p) { return contains(p) ? this : null; } @Override public NodeProxy get(final DocumentImpl document, final NodeId nodeId) { if(!this.nodeId.equals(nodeId)) { return null; } else if(this.doc.getDocId() != document.getDocId()) { return null; } else { return this; } } @Override public NodeProxy parentWithChild(final NodeProxy proxy, final boolean directParent, final boolean includeSelf, final int level) { return parentWithChild(proxy.getOwnerDocument(), proxy.getNodeId(), directParent, includeSelf); } @Override public NodeProxy parentWithChild(final DocumentImpl otherDoc, final NodeId otherId, final boolean directParent, final boolean includeSelf) { if(otherDoc.getDocId() != doc.getDocId()) { return null; } else if(includeSelf && otherId.compareTo(nodeId) == 0) { return this; } else { NodeId parentId = otherId.getParentId(); while(parentId != null) { if(parentId.compareTo(nodeId) == 0) { return this; } else if(directParent) { return null; } parentId = parentId.getParentId(); } return null; } } @Override public NodeSet getContextNodes(final int contextId) { final NewArrayNodeSet result = new NewArrayNodeSet(); ContextItem contextNode = getContext(); while(contextNode != null) { final NodeProxy p = contextNode.getNode(); p.addMatches(this); if(!result.contains(p)) { //TODO : why isn't "this" involved here ? -pb if(contextId != Expression.NO_CONTEXT_ID) { p.addContextNode(contextId, p); } result.add(p); } contextNode = contextNode.getNextDirect(); } return result; } @Override public int getState() { return 0; } @Override public boolean hasChanged(final int previousState) { return false; } @Override public void destroy(final XQueryContext context, final Sequence contextSequence) { // Nothing to do } @Override public boolean isCacheable() { return true; } @Override public int getSizeHint(final DocumentImpl document) { if(document.getDocId() == doc.getDocId()) { return 1; } else { return Constants.NO_SIZE_HINT; } } @Override public DocumentSet getDocumentSet() { return this; } @Override public Iterator<Collection> getCollectionIterator() { return new Iterator<Collection>() { boolean hasNext = true; @Override public final boolean hasNext() { return hasNext; } @Override public final Collection next() { hasNext = false; return NodeProxy.this.getOwnerDocument().getCollection(); } @Override public final void remove() { throw new UnsupportedOperationException("Remove is not implemented for NodeProxt#getCollectionIterator"); } }; } @Override public NodeSet intersection(final NodeSet other) { if(other.contains(this)) { return this; } else { return NodeSet.EMPTY_SET; } } @Override public NodeSet deepIntersection(final NodeSet other) { final NodeProxy p = other.parentWithChild(this, false, true, UNKNOWN_NODE_LEVEL); if(p == null) { return NodeSet.EMPTY_SET; } else if(!nodeId.equals(p.nodeId)) { p.addMatches(this); } return p; } @Override public NodeSet union(final NodeSet other) { if(other.isEmpty()) { return this; } final NewArrayNodeSet result = new NewArrayNodeSet(); result.addAll(other); result.add(this); return result; } @Override public NodeSet except(final NodeSet other) { return other.contains(this) ? NodeSet.EMPTY_SET : this; } @Override public NodeSet filterDocuments(final NodeSet otherSet) { final DocumentSet docs = otherSet.getDocumentSet(); if(docs.contains(doc.getDocId())) { return this; } return NodeSet.EMPTY_SET; } @Override public void setProcessInReverseOrder(final boolean inReverseOrder) { //Nothing to do } @Override public boolean getProcessInReverseOrder() { return false; } @Override public NodeSet getParents(final int contextId) { final NodeId pid = nodeId.getParentId(); if(pid == null || pid == NodeId.DOCUMENT_NODE) { return NodeSet.EMPTY_SET; } final NodeProxy parent = new NodeProxy(doc, pid, Node.ELEMENT_NODE); if(contextId != Expression.NO_CONTEXT_ID) { parent.addContextNode(contextId, this); } else { parent.copyContext(this); } parent.addMatches(this); return parent; } @Override public NodeSet getAncestors(final int contextId, final boolean includeSelf) { final NodeSet ancestors = new NewArrayNodeSet(); if(includeSelf) { ancestors.add(this); } NodeId parentID = nodeId.getParentId(); while(parentID != null) { final NodeProxy parent = new NodeProxy(getOwnerDocument(), parentID, Node.ELEMENT_NODE); if(contextId != Expression.NO_CONTEXT_ID) { parent.addContextNode(contextId, this); } else { parent.copyContext(this); } parent.addMatches(this); ancestors.add(parent); parentID = parentID.getParentId(); } return ancestors; } @Override public NodeSet selectParentChild(final NodeSet al, final int mode) { return selectParentChild(al, mode, Expression.NO_CONTEXT_ID); } @Override public NodeSet selectParentChild(final NodeSet al, final int mode, final int contextId) { return NodeSetHelper.selectParentChild(this, al, mode, contextId); } @Override public boolean matchParentChild(final NodeSet al, final int mode, final int contextId) { return NodeSetHelper.matchParentChild(this, al, mode, contextId); } /* (non-Javadoc) * @see org.exist.dom.persistent.NodeSet#selectAncestors(org.exist.dom.persistent.NodeSet, boolean, int) */ @Override public NodeSet selectAncestors(final NodeSet al, final boolean includeSelf, final int contextId) { return NodeSetHelper.selectAncestors(this, al, includeSelf, contextId); } public boolean matchAncestors(final NodeSet al, final boolean includeSelf, final int contextId) { return NodeSetHelper.matchAncestors(this, al, includeSelf, contextId); } @Override public NodeSet selectPrecedingSiblings(final NodeSet siblings, final int contextId) { return NodeSetHelper.selectPrecedingSiblings(this, siblings, contextId); } @Override public NodeSet selectFollowingSiblings(final NodeSet siblings, final int contextId) { return NodeSetHelper.selectFollowingSiblings(this, siblings, contextId); } @Override public NodeSet selectAncestorDescendant(final NodeSet al, final int mode, final boolean includeSelf, final int contextId, final boolean copyMatches) { return NodeSetHelper.selectAncestorDescendant(this, al, mode, includeSelf, contextId); } @Override public boolean matchAncestorDescendant(final NodeSet al, final int mode, final boolean includeSelf, final int contextId, final boolean copyMatches) { return NodeSetHelper.matchAncestorDescendant(this, al, mode, includeSelf, contextId); } @Override public NodeSet selectPreceding(final NodeSet preceding, final int contextId) throws XPathException { return NodeSetHelper.selectPreceding(this, preceding); } @Override public NodeSet selectPreceding(final NodeSet preceding, final int position, final int contextId) throws XPathException, UnsupportedOperationException { throw new UnsupportedOperationException(); } @Override public NodeSet selectFollowing(final NodeSet following, final int contextId) throws XPathException { return NodeSetHelper.selectFollowing(this, following); } @Override public NodeSet selectFollowing(final NodeSet following, final int position, final int contextId) throws XPathException { throw new UnsupportedOperationException(); } @Override public NodeSet directSelectAttribute(final DBBroker broker, final NodeTest test, final int contextId) { if(nodeType != UNKNOWN_NODE_TYPE && nodeType != Node.ELEMENT_NODE) { return NodeSet.EMPTY_SET; } try { NewArrayNodeSet result = null; final IEmbeddedXMLStreamReader reader = broker.getXMLStreamReader(this, true); int status = reader.next(); if(status != XMLStreamReader.START_ELEMENT) { return NodeSet.EMPTY_SET; } final int attrs = reader.getAttributeCount(); for(int i = 0; i < attrs; i++) { status = reader.next(); if(status != XMLStreamReader.ATTRIBUTE) { break; } final AttrImpl attr = (AttrImpl) reader.getNode(); if(test.matches(attr)) { final NodeProxy child = new NodeProxy(attr); if(Expression.NO_CONTEXT_ID != contextId) { child.addContextNode(contextId, this); } else { child.copyContext(this); } if(!test.isWildcardTest()) { return child; } if(result == null) { result = new NewArrayNodeSet(); } result.add(child); } } return result == null ? NodeSet.EMPTY_SET : result; } catch(final IOException e) { throw new RuntimeException(e.getMessage(), e); } catch(final XMLStreamException e) { throw new RuntimeException(e.getMessage(), e); } } public NodeSet directSelectChild(final QName qname, final int contextId) { if(nodeType != UNKNOWN_NODE_TYPE && nodeType != Node.ELEMENT_NODE) { return NodeSet.EMPTY_SET; } final NodeImpl node = (NodeImpl) getNode(); if(node.getNodeType() != Node.ELEMENT_NODE) { return NodeSet.EMPTY_SET; } final NodeList children = node.getChildNodes(); if(children.getLength() == 0) { return NodeSet.EMPTY_SET; } final NewArrayNodeSet result = new NewArrayNodeSet(); IStoredNode<?> child; for(int i = 0; i < children.getLength(); i++) { child = (IStoredNode<?>) children.item(i); if(child.getQName().equals(qname)) { final NodeProxy p = new NodeProxy(doc, child.getNodeId(), Node.ELEMENT_NODE, child.getInternalAddress()); if(Expression.NO_CONTEXT_ID != contextId) { p.addContextNode(contextId, this); } else { p.copyContext(this); } p.addMatches(this); result.add(p); } } return result; } @Override public String toString() { if(nodeId == NodeId.DOCUMENT_NODE) { return "Document node for " + doc.getDocId(); } else { return doc.getNode(nodeId).getNodeName(); } } private static final class SingleNodeIterator implements NodeSetIterator, SequenceIterator { private boolean hasNext = true; private NodeProxy node; public SingleNodeIterator(final NodeProxy node) { this.node = node; } @Override public final boolean hasNext() { return hasNext; } @Override public final NodeProxy next() { if(!hasNext) { throw new NoSuchElementException(); } else { hasNext = false; return node; } } @Override public final NodeProxy peekNode() { return node; } @Override public final void remove() { throw new UnsupportedOperationException("remove is not implemented for SingleNodeIterator"); } @Override public final Item nextItem() { if(!hasNext) { return null; } else { hasNext = false; return node; } } @Override public final void setPosition(final NodeProxy proxy) { node = proxy; hasNext = true; } } /** * ********************************************* * Methods of MutableDocumentSet * ********************************************** */ @Override public Iterator<DocumentImpl> getDocumentIterator() { return new Iterator<DocumentImpl>() { private boolean hasMore = true; @Override public final boolean hasNext() { return hasMore; } @Override public final DocumentImpl next() { if(!hasMore) { throw new NoSuchElementException(); } else { hasMore = false; return doc; } } @Override public final void remove() { throw new UnsupportedOperationException("remove is not implemented for NodeProxy#getDocumentIterator"); } }; } @Override public int getDocumentCount() { return 1; } public DocumentImpl getDoc() { return doc; } @Override public DocumentImpl getDoc(final int docId) { if(docId == this.doc.getDocId()) { return this.doc; } return null; } @Override public XmldbURI[] getNames() { return new XmldbURI[]{ this.doc.getURI() }; } @Override public DocumentSet intersection(final DocumentSet other) { if(other.contains(doc.getDocId())) { final DefaultDocumentSet r = new DefaultDocumentSet(); r.add(doc); return r; } else { return DefaultDocumentSet.EMPTY_DOCUMENT_SET; } } @Override public boolean contains(final DocumentSet other) { if(other.getDocumentCount() > 1) { return false; } else if(other.getDocumentCount() == 0) { return true; } else { return other.contains(doc.getDocId()); } } @Override public boolean contains(final int docId) { return doc.getDocId() == docId; } @Override public NodeSet docsToNodeSet() { return new NodeProxy(doc, NodeId.DOCUMENT_NODE); } @Override public void lock(final DBBroker broker, final boolean exclusive, final boolean checkExisting) throws LockException { final Lock docLock = doc.getUpdateLock(); docLock.acquire(exclusive ? LockMode.WRITE_LOCK : LockMode.READ_LOCK); } @Override public void unlock(final boolean exclusive) { final Lock docLock = doc.getUpdateLock(); if(exclusive) { docLock.release(LockMode.WRITE_LOCK); } else if(docLock.isLockedForRead(Thread.currentThread())) { docLock.release(LockMode.READ_LOCK); } } @Override public boolean equalDocs(final DocumentSet other) { if(this == other) { // we are comparing the same objects return true; } else if(other.getDocumentCount() != 1) { return false; } else { return other.contains(doc.getDocId()); } } @Override public boolean directMatchAttribute(final DBBroker broker, final NodeTest test, final int contextId) { if(nodeType != UNKNOWN_NODE_TYPE && nodeType != Node.ELEMENT_NODE) { return false; } try { final IEmbeddedXMLStreamReader reader = broker.getXMLStreamReader(this, true); int status = reader.next(); if(status != XMLStreamReader.START_ELEMENT) { return false; } final int attrs = reader.getAttributeCount(); for(int i = 0; i < attrs; i++) { status = reader.next(); if(status != XMLStreamReader.ATTRIBUTE) { break; } final AttrImpl attr = (AttrImpl) reader.getNode(); if(test.matches(attr)) { return true; } } return false; } catch(final IOException e) { throw new RuntimeException(e.getMessage(), e); } catch(final XMLStreamException e) { throw new RuntimeException(e.getMessage(), e); } } public boolean directMatchChild(final QName qname, final int contextId) { if(nodeType != UNKNOWN_NODE_TYPE && nodeType != Node.ELEMENT_NODE) { return false; } final NodeImpl node = (NodeImpl) getNode(); if(node.getNodeType() != Node.ELEMENT_NODE) { return false; } final NodeList children = node.getChildNodes(); if(children.getLength() == 0) { return false; } IStoredNode<?> child; for(int i = 0; i < children.getLength(); i++) { child = (IStoredNode<?>) children.item(i); if(child.getQName().equals(qname)) { return true; } } return false; } }