/*
* eXist Open Source Native XML Database
* Copyright (C) 2000-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 org.exist.EXistException;
import org.exist.dom.QName;
import org.exist.numbering.NodeId;
import org.exist.stax.ExtendedXMLStreamReader;
import org.exist.stax.IEmbeddedXMLStreamReader;
import org.exist.storage.DBBroker;
import org.exist.storage.NodePath;
import org.exist.storage.Signatures;
import org.exist.storage.dom.INodeIterator;
import org.exist.util.pool.NodePool;
import org.exist.xquery.Constants;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import java.io.IOException;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
/**
* The base class for all persistent DOM nodes in the database.
*
* @author Wolfgang Meier <meier@ifs.tu-darmstadt.de>
*/
public abstract class StoredNode<T extends StoredNode> extends NodeImpl<T> implements Visitable, NodeHandle, IStoredNode<T> {
public static final int LENGTH_SIGNATURE_LENGTH = 1; //sizeof byte
public static final long UNKNOWN_NODE_IMPL_ADDRESS = -1;
protected NodeId nodeId = null;
protected DocumentImpl ownerDocument = null;
private long internalAddress = UNKNOWN_NODE_IMPL_ADDRESS;
protected final short nodeType;
/**
* Creates a new <code>StoredNode</code> instance.
*
* @param nodeType a <code>short</code> value
*/
protected StoredNode(final short nodeType) {
this.nodeType = nodeType;
}
/**
* Creates a new <code>StoredNode</code> instance.
*
* @param nodeType a <code>short</code> value
* @param nodeId a <code>NodeId</code> value
*/
protected StoredNode(final short nodeType, final NodeId nodeId) {
this.nodeType = nodeType;
this.nodeId = nodeId;
}
protected StoredNode(final short nodeType, final NodeId nodeId, final DocumentImpl ownerDocument, long internalAddress) {
this(nodeType, nodeId);
this.ownerDocument = ownerDocument;
this.internalAddress = internalAddress;
}
protected StoredNode(final StoredNode other) {
this.nodeType = other.nodeType;
this.nodeId = other.nodeId;
this.internalAddress = other.internalAddress;
this.ownerDocument = other.ownerDocument;
}
/**
* Extracts just the details of the StoredNode
*/
public StoredNode extract() {
return new StoredNode(this) {
};
}
/**
* Reset this object to its initial state. Required by the
* parser to be able to reuse node objects.
*/
public void clear() {
this.nodeId = null;
this.internalAddress = UNKNOWN_NODE_IMPL_ADDRESS;
}
@Override
public byte[] serialize() {
throw new DOMException(DOMException.INVALID_ACCESS_ERR, "Can't serialize " + getClass().getName());
}
/**
* Read a node from the specified byte array.
* <p/>
* This checks the node type and calls the {@link #deserialize(byte[], int, int, DocumentImpl, boolean)}
* method of the corresponding node class.
*
* @param data
* @param start
* @param len
* @param doc
*/
public static StoredNode deserialize(final byte[] data, final int start, final int len, final DocumentImpl doc) {
return deserialize(data, start, len, doc, false);
}
/**
* Read a node from the specified byte array.
* <p/>
* This checks the node type and calls the {@link #deserialize(byte[], int, int, DocumentImpl, boolean)}
* method of the corresponding node class. The node will be allocated in the pool
* and should be released once it is no longer needed.
*
* @param data
* @param start
* @param len
* @param doc
*/
public static StoredNode deserialize(final byte[] data, final int start, final int len, final DocumentImpl doc, boolean pooled) {
final short type = Signatures.getType(data[start]);
switch(type) {
case Node.TEXT_NODE:
return TextImpl.deserialize(data, start, len, doc, pooled);
case Node.ELEMENT_NODE:
return ElementImpl.deserialize(data, start, len, doc, pooled);
case Node.ATTRIBUTE_NODE:
return AttrImpl.deserialize(data, start, len, doc, pooled);
case Node.PROCESSING_INSTRUCTION_NODE:
return ProcessingInstructionImpl.deserialize(data, start, len, doc, pooled);
case Node.COMMENT_NODE:
return CommentImpl.deserialize(data, start, len, doc, pooled);
case Node.CDATA_SECTION_NODE:
return CDATASectionImpl.deserialize(data, start, len, doc, pooled);
default:
LOG.error("Unknown node type: " + type);
Thread.dumpStack();
return null;
}
}
@Override
public QName getQName() {
switch(getNodeType()) {
case Node.DOCUMENT_NODE:
return QName.DOCUMENT_QNAME;
case Node.TEXT_NODE:
return QName.TEXT_QNAME;
case Node.COMMENT_NODE:
return QName.COMMENT_QNAME;
case Node.DOCUMENT_TYPE_NODE:
return QName.DOCTYPE_QNAME;
default:
LOG.error("Unknown node type: " + getNodeType());
return null;
}
}
@Override
public void setQName(final QName qname) {
//do nothing
}
@Override
public boolean equals(final Object obj) {
if(!(obj instanceof StoredNode)) {
return false;
}
return ((StoredNode) obj).nodeId.equals(nodeId);
}
@Override
public void setNodeId(final NodeId dln) {
this.nodeId = dln;
}
public NodeId getNodeId() {
return nodeId;
}
@Override
public long getInternalAddress() {
return internalAddress;
}
@Override
public void setInternalAddress(final long internalAddress) {
this.internalAddress = internalAddress;
}
/**
* Returns true if the node was modified recently and nodes
* were inserted at the start or in the middle of its children.
*
* @return TRUE when node is 'dirty'
*/
public boolean isDirty() {
return true;
}
@Override
public void setDirty(final boolean dirty) {
//Nothing to do
}
@Override
public short getNodeType() {
return this.nodeType;
}
@Override
public DocumentImpl getOwnerDocument() {
return ownerDocument;
}
@Override
public void setOwnerDocument(final DocumentImpl ownerDocument) {
this.ownerDocument = ownerDocument;
}
@Override
public Node getParentNode() {
final NodeId parentId = nodeId.getParentId();
if(parentId == NodeId.DOCUMENT_NODE) {
return ownerDocument;
}
// Filter out the temporary nodes wrapper element
if(parentId.getTreeLevel() == 1 && getOwnerDocument().getCollection().isTempCollection()) {
return ownerDocument;
}
return ownerDocument.getNode(parentId);
}
@Override
public StoredNode getParentStoredNode() {
final Node parent = getParentNode();
return parent instanceof StoredNode ? (StoredNode) parent : null;
}
@Override
public Node getPreviousSibling() {
final StoredNode parent = getParentStoredNode();
if(parent == null) {
return null;
}
if(parent.isDirty()) {
try(final DBBroker broker = ownerDocument.getBrokerPool().getBroker()) {
final IEmbeddedXMLStreamReader reader = broker.getXMLStreamReader(parent, true);
final int level = nodeId.getTreeLevel();
IStoredNode last = null;
while(reader.hasNext()) {
final int status = reader.next();
final NodeId currentId = (NodeId) reader.getProperty(ExtendedXMLStreamReader.PROPERTY_NODE_ID);
if(status != XMLStreamConstants.END_ELEMENT && currentId.getTreeLevel() == level) {
if(currentId.equals(nodeId)) {
return last;
}
last = reader.getNode();
}
}
} catch(final IOException e) {
LOG.error("Internal error while reading child nodes: " + e.getMessage(), e);
//TODO : throw exception -pb
} catch(final XMLStreamException e) {
LOG.error("Internal error while reading child nodes: " + e.getMessage(), e);
//TODO : throw exception -pb
} catch(final EXistException e) {
LOG.error("Internal error while reading child nodes: " + e.getMessage(), e);
//TODO : throw exception -pb
}
return null;
}
final NodeId firstChild = parent.getNodeId().newChild();
if(nodeId.equals(firstChild)) {
return null;
}
final NodeId siblingId = nodeId.precedingSibling();
return ownerDocument.getNode(siblingId);
}
@Override
public Node getNextSibling() {
if(nodeId.getTreeLevel() == 2 && getOwnerDocument().getCollection().isTempCollection()) {
return null;
}
final StoredNode parent = getParentStoredNode();
if(parent == null) {
return null;
}
if(parent.isDirty()) {
try(final DBBroker broker = ownerDocument.getBrokerPool().getBroker()) {
final IEmbeddedXMLStreamReader reader = broker.getXMLStreamReader(parent, true);
final int level = nodeId.getTreeLevel();
while(reader.hasNext()) {
final int status = reader.next();
final NodeId currentId = (NodeId) reader.getProperty(ExtendedXMLStreamReader.PROPERTY_NODE_ID);
if(status != XMLStreamConstants.END_ELEMENT
&& currentId.getTreeLevel() == level
&& currentId.compareTo(nodeId) > 0) {
return reader.getNode();
}
}
} catch(final IOException e) {
LOG.error("Internal error while reading child nodes: " + e.getMessage(), e);
//TODO : throw exception -pb
} catch(final XMLStreamException e) {
LOG.error("Internal error while reading child nodes: " + e.getMessage(), e);
//TODO : throw exception -pb
} catch(final EXistException e) {
LOG.error("Internal error while reading child nodes: " + e.getMessage(), e);
//TODO : throw exception -pb
}
return null;
}
final NodeId siblingId = nodeId.nextSibling();
return ownerDocument.getNode(siblingId);
}
protected IStoredNode getLastNode(final IStoredNode node) {
if(!node.hasChildNodes()) {
return node;
}
try(final DBBroker broker = ownerDocument.getBrokerPool().getBroker()) {
final IEmbeddedXMLStreamReader reader = broker.getXMLStreamReader(node, true);
while(reader.hasNext()) {
reader.next();
}
return reader.getPreviousNode();
} catch(final IOException e) {
LOG.error("Internal error while reading child nodes: " + e.getMessage(), e);
//TODO : throw exception -pb
} catch(final XMLStreamException e) {
LOG.error("Internal error while reading child nodes: " + e.getMessage(), e);
//TODO : throw exception -pb
} catch(final EXistException e) {
LOG.error("Internal error while reading child nodes: " + e.getMessage(), e);
//TODO : throw exception -pb
}
return null;
}
// protected StoredNode getLastNode(final Iterator<StoredNode> iterator, final StoredNode node) {
// if(!node.hasChildNodes()) {
// return node;
// }
// final int children = node.getChildCount();
// StoredNode next = null;
// for(int i = 0; i < children; i++) {
// next = iterator.next();
// //Recursivity helps taversing...
// next = getLastNode(iterator, next);
// }
// return next;
// }
@Override
public NodePath getPath() {
final NodePath path = new NodePath();
if(getNodeType() == Node.ELEMENT_NODE) {
path.addComponent(getQName());
}
NodeImpl parent = (NodeImpl) getParentNode();
while(parent != null && parent.getNodeType() != Node.DOCUMENT_NODE) {
path.addComponentAtStart(parent.getQName());
parent = (NodeImpl) parent.getParentNode();
}
return path;
}
@Override
public NodePath getPath(final NodePath parentPath) {
if(getNodeType() == Node.ELEMENT_NODE) {
parentPath.addComponent(getQName());
}
return parentPath;
}
@Override
public String toString() {
return nodeId.toString() + '\t' + getQName();
}
public String toString(final boolean top) {
return toString();
}
/**
* Release all memory resources hold by this node.
*/
@Override
public void release() {
ownerDocument = null;
clear();
NodePool.getInstance().returnNode(this);
}
public boolean accept(final NodeVisitor visitor) {
try(final DBBroker broker = ownerDocument.getBrokerPool().getBroker();
final INodeIterator iterator = broker.getNodeIterator(this)) {
iterator.next();
return accept(iterator, visitor);
} catch(final EXistException | IOException e) {
LOG.error("Exception while reading node: " + e.getMessage(), e);
//TODO : throw exception -pb
}
return false;
}
@Override
public boolean accept(final INodeIterator iterator, final NodeVisitor visitor) {
return visitor.visit(this); //TODO iterator is not used here?
}
@Override
public int compareTo(final StoredNode other) {
if(other.ownerDocument == ownerDocument) {
return nodeId.compareTo(other.nodeId);
} else if(ownerDocument.getDocId() < other.ownerDocument.getDocId()) {
return Constants.INFERIOR;
} else {
return Constants.SUPERIOR;
}
}
@Override
public boolean isSameNode(final Node other) {
// This function is used by Saxon in some circumstances, and is required for proper Saxon operation.
if(other instanceof IStoredNode) {
return (this.nodeId.equals(((IStoredNode<?>) other).getNodeId()) &&
this.ownerDocument.getDocId() == ((IStoredNode<? extends IStoredNode>) other).getOwnerDocument().getDocId());
} else {
return false;
}
}
}