/* * 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.memtree; import org.exist.dom.NodeListImpl; import org.exist.dom.NodeProxy; import org.exist.dom.QName; import org.exist.numbering.NodeId; import org.exist.storage.ElementValue; import org.exist.storage.serializers.Serializer; import org.exist.util.hashtable.NamePool; import org.exist.util.serializer.AttrList; import org.exist.util.serializer.Receiver; import org.exist.xmldb.XmldbURI; import org.exist.xquery.NodeTest; import org.exist.xquery.XPathException; import org.exist.xquery.XQueryContext; import org.exist.xquery.value.Sequence; import org.exist.xquery.value.Type; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.Comment; import org.w3c.dom.DOMConfiguration; import org.w3c.dom.DOMException; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.DocumentFragment; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.EntityReference; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; import org.xml.sax.SAXException; import java.util.Arrays; /** * An in-memory implementation of Document. * * This implementation stores all node data in the document object. Nodes from * another document, i.e. a persistent document in the database, can be stored * as reference nodes, i.e. the nodes are not copied into this document object. * Instead a reference is inserted which will only be expanded during * serialization. * * @author wolf */ public class DocumentImpl extends NodeImpl implements Document { private static long nextDocId = 0; private static long createDocId() { return nextDocId++; } protected XQueryContext context; protected NamePool namePool; // holds the node type of a node protected short[] nodeKind = null; // the tree level of a node protected short[] treeLevel; // the node number of the next sibling protected int[] next; // pointer into the namePool protected QName[] nodeName; protected NodeId[] nodeId; protected int[] alpha; protected int[] alphaLen; protected char[] characters = null; protected int nextChar = 0; // attributes protected QName[] attrName; protected int[] attrType; protected NodeId[] attrNodeId; protected int[] attrParent; protected String[] attrValue; protected int nextAttr = 0; // namespaces protected int[] namespaceParent = null; protected QName[] namespaceCode = null; protected int nextNamespace = 0; // the current number of nodes in the doc protected int size = 1; protected int documentRootNode = -1; protected String documentURI = null; // reference nodes (link to an external, persistent document fragment) protected NodeProxy references[] = null; protected int nextRef = 0; protected long docId; private final static int NODE_SIZE = 16; private final static int ATTR_SIZE = 8; private final static int CHAR_BUF_SIZE = 256; private final static int REF_SIZE = 8; boolean explicitCreation = false; public DocumentImpl(XQueryContext context) { this(context, false); } public DocumentImpl(XQueryContext context, boolean explicitCreation) { super(null, 0); this.context = context; this.explicitCreation = explicitCreation; this.docId = createDocId(); if (context == null) namePool = new NamePool(); else namePool = context.getSharedNamePool(); } private void init() { nodeKind = new short[NODE_SIZE]; treeLevel = new short[NODE_SIZE]; next = new int[NODE_SIZE]; Arrays.fill(next, -1); nodeName = new QName[NODE_SIZE]; nodeId = new NodeId[NODE_SIZE]; alpha = new int[NODE_SIZE]; alphaLen = new int[NODE_SIZE]; Arrays.fill(alphaLen, -1); attrName = new QName[ATTR_SIZE]; attrParent = new int[ATTR_SIZE]; attrValue = new String[ATTR_SIZE]; attrType = new int[ATTR_SIZE]; attrNodeId = new NodeId[NODE_SIZE]; treeLevel[0] = 0; nodeKind[0] = Node.DOCUMENT_NODE; document = this; } public void reset() { size = 0; nextChar = 0; nextAttr = 0; nextRef = 0; references = null; } public int getSize() { return size; } public int addNode(short kind, short level, QName qname) { if (nodeKind == null) init(); if (size == nodeKind.length) grow(); nodeKind[size] = kind; treeLevel[size] = level; nodeName[size] = (qname != null ? namePool.getSharedName(qname) : null); alpha[size] = -1; // undefined next[size] = -1; return size++; } public void addChars(int nodeNr, char[] ch, int start, int len) { if (nodeKind == null) init(); if (characters == null) characters = new char[len > CHAR_BUF_SIZE ? len : CHAR_BUF_SIZE]; else if (nextChar + len >= characters.length) { int newLen = (characters.length * 3) / 2; if (newLen < nextChar + len) newLen = nextChar + len; char[] nc = new char[newLen]; System.arraycopy(characters, 0, nc, 0, characters.length); characters = nc; } alpha[nodeNr] = nextChar; alphaLen[nodeNr] = len; System.arraycopy(ch, start, characters, nextChar, len); nextChar += len; } public void addChars(int nodeNr, CharSequence s) { if (nodeKind == null) init(); int len = s.length(); if (characters == null) characters = new char[len > CHAR_BUF_SIZE ? len : CHAR_BUF_SIZE]; else if (nextChar + len >= characters.length) { int newLen = (characters.length * 3) / 2; if (newLen < nextChar + len) newLen = nextChar + len; char[] nc = new char[newLen]; System.arraycopy(characters, 0, nc, 0, characters.length); characters = nc; } alpha[nodeNr] = nextChar; alphaLen[nodeNr] = len; for (int i = 0; i < len; i++) { characters[nextChar++] = s.charAt(i); } } public void appendChars(int nodeNr, char[] ch, int start, int len) { if (characters == null) characters = new char[len > CHAR_BUF_SIZE ? len : CHAR_BUF_SIZE]; else if (nextChar + len >= characters.length) { int newLen = (characters.length * 3) / 2; if (newLen < nextChar + len) newLen = nextChar + len; char[] nc = new char[newLen]; System.arraycopy(characters, 0, nc, 0, characters.length); characters = nc; } alphaLen[nodeNr] = alphaLen[nodeNr] + len; System.arraycopy(ch, start, characters, nextChar, len); nextChar += len; } public void appendChars(int nodeNr, CharSequence s) { int len = s.length(); if (characters == null) characters = new char[len > CHAR_BUF_SIZE ? len : CHAR_BUF_SIZE]; else if (nextChar + len >= characters.length) { int newLen = (characters.length * 3) / 2; if (newLen < nextChar + len) newLen = nextChar + len; char[] nc = new char[newLen]; System.arraycopy(characters, 0, nc, 0, characters.length); characters = nc; } alphaLen[nodeNr] = alphaLen[nodeNr] + len; for (int i = 0; i < len; i++) { characters[nextChar++] = s.charAt(i); } } public void addReferenceNode(int nodeNr, NodeProxy proxy) { if (nodeKind == null) init(); if (references == null || nextRef == references.length) growReferences(); references[nextRef] = proxy; alpha[nodeNr] = nextRef++; } public void replaceReferenceNode(int nodeNr, CharSequence ch) { nodeKind[nodeNr] = Node.TEXT_NODE; references[alpha[nodeNr]] = null; addChars(nodeNr, ch); } public boolean hasReferenceNodes() { return references != null && references[0] != null; } public int addAttribute(int nodeNr, QName qname, String value, int type) throws DOMException { if (nodeKind == null) init(); if (nodeNr > 0 && nodeKind[nodeNr] != Node.ELEMENT_NODE) throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, "XQTY0024: An attribute node cannot follow a node that is not an attribute node."); int prevAttr = nextAttr - 1; // check if an attribute with the same qname exists in the parent element while (nodeNr > 0 && prevAttr > -1 && attrParent[prevAttr] == nodeNr) { QName prevQn = attrName[prevAttr--]; if (prevQn.equalsSimple(qname)) throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, "Error XQDY0025: element has more than one attribute '" + qname + "'"); } if (nextAttr == attrName.length) growAttributes(); qname.setNameType(ElementValue.ATTRIBUTE); attrParent[nextAttr] = nodeNr; attrName[nextAttr] = namePool.getSharedName(qname); attrValue[nextAttr] = value; attrType[nextAttr] = type; if (alpha[nodeNr] < 0) alpha[nodeNr] = nextAttr; return nextAttr++; } public int addNamespace(int nodeNr, QName qname) { if(nodeKind == null) init(); if(namespaceCode == null || nextNamespace == namespaceCode.length) growNamespaces(); namespaceCode[nextNamespace] = namePool.getSharedName(qname); namespaceParent[nextNamespace] = nodeNr; if(alphaLen[nodeNr] < 0) { alphaLen[nodeNr] = nextNamespace; } return nextNamespace++; } public short getTreeLevel(int nodeNr) { return treeLevel[nodeNr]; } public int getLastNode() { return size - 1; } public DocumentImpl getDocument() { return this; } public short getNodeType(int nodeNr) { if (nodeKind == null || nodeNr < 0) return -1; return nodeKind[nodeNr]; } private void grow() { int newSize = (size * 3) / 2; short[] newNodeKind = new short[newSize]; System.arraycopy(nodeKind, 0, newNodeKind, 0, size); nodeKind = newNodeKind; short[] newTreeLevel = new short[newSize]; System.arraycopy(treeLevel, 0, newTreeLevel, 0, size); treeLevel = newTreeLevel; int[] newNext = new int[newSize]; Arrays.fill(newNext, -1); System.arraycopy(next, 0, newNext, 0, size); next = newNext; QName[] newNodeName = new QName[newSize]; System.arraycopy(nodeName, 0, newNodeName, 0, size); nodeName = newNodeName; NodeId[] newNodeId = new NodeId[newSize]; System.arraycopy(nodeId, 0, newNodeId, 0, size); nodeId = newNodeId; int[] newAlpha = new int[newSize]; System.arraycopy(alpha, 0, newAlpha, 0, size); alpha = newAlpha; int[] newAlphaLen = new int[newSize]; Arrays.fill(newAlphaLen, -1); System.arraycopy(alphaLen, 0, newAlphaLen, 0, size); alphaLen = newAlphaLen; } private void growAttributes() { int size = attrName.length; int newSize = (size * 3) / 2; QName[] newAttrName = new QName[newSize]; System.arraycopy(attrName, 0, newAttrName, 0, size); attrName = newAttrName; int[] newAttrParent = new int[newSize]; System.arraycopy(attrParent, 0, newAttrParent, 0, size); attrParent = newAttrParent; String[] newAttrValue = new String[newSize]; System.arraycopy(attrValue, 0, newAttrValue, 0, size); attrValue = newAttrValue; int[] newAttrType = new int[newSize]; System.arraycopy(attrType, 0, newAttrType, 0, size); attrType = newAttrType; NodeId[] newNodeId = new NodeId[newSize]; System.arraycopy(attrNodeId, 0, newNodeId, 0, size); attrNodeId = newNodeId; } private void growReferences() { if (references == null) references = new NodeProxy[REF_SIZE]; else { int size = references.length; int newSize = (size * 3) / 2; NodeProxy newReferences[] = new NodeProxy[newSize]; System.arraycopy(references, 0, newReferences, 0, size); references = newReferences; } } private void growNamespaces() { if (namespaceCode == null) { namespaceCode = new QName[5]; namespaceParent = new int[5]; } else { int size = namespaceCode.length; int newSize = (size * 3) / 2; QName[] newCodes = new QName[newSize]; System.arraycopy(namespaceCode, 0, newCodes, 0, size); namespaceCode = newCodes; int[] newParents = new int[newSize]; System.arraycopy(namespaceParent, 0, newParents, 0, size); namespaceParent = newParents; } } public NodeImpl getAttribute(int nodeNr) throws DOMException { return new AttributeImpl(this, nodeNr); } public NodeImpl getNamespaceNode(int nodeNr) throws DOMException { return new NamespaceNode(this, nodeNr); } public NodeImpl getNode(int nodeNr) throws DOMException { if (nodeNr == 0) return this; if (nodeNr >= size) throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "node not found"); NodeImpl node; switch (nodeKind[nodeNr]) { case Node.ELEMENT_NODE: node = new ElementImpl(this, nodeNr); break; case Node.TEXT_NODE: node = new TextImpl(this, nodeNr); break; case Node.COMMENT_NODE: node = new CommentImpl(this, nodeNr); break; case Node.PROCESSING_INSTRUCTION_NODE: node = new ProcessingInstructionImpl(this, nodeNr); break; case Node.CDATA_SECTION_NODE: node = new CDATASectionImpl(this, nodeNr); break; case NodeImpl.REFERENCE_NODE: node = new ReferenceNode(this, nodeNr); break; default: throw new DOMException(DOMException.NOT_FOUND_ERR, "node not found"); } return node; } public NodeImpl getLastAttr() { if (nextAttr == 0) return null; return new AttributeImpl(this, nextAttr - 1); } /* * (non-Javadoc) * * @see org.w3c.dom.Node#getParentNode() */ public Node getParentNode() { return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#getDoctype() */ public DocumentType getDoctype() { return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#getImplementation() */ public DOMImplementation getImplementation() { return new DOMImplementation() { public Document createDocument(String namespaceURI, String qualifiedName, DocumentType doctype) throws DOMException { return null; } public DocumentType createDocumentType(String qualifiedName, String publicId, String systemId) throws DOMException { return null; } public Object getFeature(String feature, String version) { return null; } public boolean hasFeature(String feature, String version) { return "XML".equals(feature) && ("1.0".equals(version) || "2.0".equals(version)); } }; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#getDocumentElement() */ public Element getDocumentElement() { if (size == 1) return null; int nodeNr = 1; while (nodeKind[nodeNr] != Node.ELEMENT_NODE) { if (next[nodeNr] < nodeNr) { return null; } else nodeNr = next[nodeNr]; } return (Element) getNode(nodeNr); } /* * (non-Javadoc) * * @see org.w3c.dom.Node#getFirstChild() */ public Node getFirstChild() { if (size > 1) return getNode(1); else return null; } public int getAttributesCountFor(int nodeNumber) { int count = 0; int attr = alpha[nodeNumber]; if (-1 < attr) { while (attr < nextAttr && attrParent[attr++] == nodeNumber) { ++count; } } return count; } public int getNamespacesCountFor(int nodeNumber) { int count = 0; int ns = alphaLen[nodeNumber]; if (-1 < ns) { while (ns < nextNamespace && namespaceParent[ns++] == nodeNumber) { ++count; } } return count; } public int getChildCountFor(int nr) { int count = 0; //short level = (short)(treeLevel[nr] + 1); int nextNode = getFirstChildFor(nr); while (nextNode > nr) { ++count; nextNode = next[nextNode]; } return count; } public int getFirstChildFor(int nodeNumber) { short level = treeLevel[nodeNumber]; int nextNode = nodeNumber + 1; if (nextNode < size && treeLevel[nextNode] > level) { return nextNode; } else return -1; } public int getNextSiblingFor(int nodeNumber) { int nextNr = next[nodeNumber]; return nextNr < nodeNumber ? -1 : nextNr; } /** * The method <code>getParentNodeFor</code> * * @param nodeNumber an <code>int</code> value * @return an <code>int</code> value */ public int getParentNodeFor(int nodeNumber) { int nextNode = next[nodeNumber]; while (nextNode > nodeNumber) { nextNode = next[nextNode]; } return nextNode; } public void selectChildren(NodeTest test, Sequence result) throws XPathException { if (size == 1) return; NodeImpl next = (NodeImpl) getFirstChild(); while (next != null) { if (test.matches(next)) result.add(next); next = (NodeImpl) next.getNextSibling(); } } public void selectDescendants(boolean includeSelf, NodeTest test, Sequence result) throws XPathException { if (includeSelf && test.matches(this)) result.add(this); if (size == 1) return; NodeImpl next = (NodeImpl) getFirstChild(); while (next != null) { if (test.matches(next)) result.add(next); next.selectDescendants(includeSelf, test, result); next = (NodeImpl) next.getNextSibling(); } } public void selectDescendantAttributes(NodeTest test, Sequence result) throws XPathException { if (size == 1) return; NodeImpl next = (NodeImpl) getFirstChild(); while (next != null) { if (test.matches(next)) result.add(next); next.selectDescendantAttributes(test, result); next = (NodeImpl) next.getNextSibling(); } } public NodeImpl selectById(String id) throws XPathException { if (size == 1) return null; ElementImpl root = (ElementImpl) getDocumentElement(); if (hasIdAttribute(root.getNodeNumber(), id)) return root; int treeLevel = this.treeLevel[root.getNodeNumber()]; int nextNode = root.getNodeNumber(); while (++nextNode < document.size && document.treeLevel[nextNode] > treeLevel) { if (document.nodeKind[nextNode] == Node.ELEMENT_NODE && hasIdAttribute(nextNode, id)) return getNode(nextNode); } return null; } public NodeImpl selectByIdref(String id) throws XPathException { if (size == 1) return null; ElementImpl root = (ElementImpl) getDocumentElement(); AttributeImpl attr = getIdrefAttribute(root.getNodeNumber(), id); if (attr != null) return attr; int treeLevel = this.treeLevel[root.getNodeNumber()]; int nextNode = root.getNodeNumber(); while (++nextNode < document.size && document.treeLevel[nextNode] > treeLevel) { if (document.nodeKind[nextNode] == Node.ELEMENT_NODE) { attr = getIdrefAttribute(nextNode, id); if (attr != null) return attr; } } return null; } private boolean hasIdAttribute(int nodeNumber, String id) { int attr = document.alpha[nodeNumber]; if (-1 < attr) { while (attr < document.nextAttr && document.attrParent[attr] == nodeNumber) { if (document.attrType[attr] == AttributeImpl.ATTR_ID_TYPE && id.equals(document.attrValue[attr])) return true; ++attr; } } return false; } private AttributeImpl getIdrefAttribute(int nodeNumber, String id) { int attr = document.alpha[nodeNumber]; if (-1 < attr) { while (attr < document.nextAttr && document.attrParent[attr] == nodeNumber) { if (document.attrType[attr] == AttributeImpl.ATTR_IDREF_TYPE && id.equals(document.attrValue[attr])) return new AttributeImpl(this, attr); ++attr; } } return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#createElement(java.lang.String) */ public Element createElement(String arg0) throws DOMException { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#createDocumentFragment() */ public DocumentFragment createDocumentFragment() { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#createTextNode(java.lang.String) */ public Text createTextNode(String arg0) { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#createComment(java.lang.String) */ public Comment createComment(String arg0) { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#createCDATASection(java.lang.String) */ public CDATASection createCDATASection(String arg0) throws DOMException { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#createProcessingInstruction(java.lang.String, * java.lang.String) */ public ProcessingInstruction createProcessingInstruction(String arg0, String arg1) throws DOMException { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#createAttribute(java.lang.String) */ public Attr createAttribute(String arg0) throws DOMException { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#createEntityReference(java.lang.String) */ public EntityReference createEntityReference(String arg0) throws DOMException { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#getElementsByTagName(java.lang.String) */ public NodeList getElementsByTagName(String name) { NodeListImpl nl = new NodeListImpl(); //int nodeNr = 1; for(int i = 1; i < size; i++) { if (nodeKind[i] == Node.ELEMENT_NODE) { QName qn = nodeName[i]; if(qn.getStringValue().equals(name)) nl.add(getNode(i)); } } return nl; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#importNode(org.w3c.dom.Node, boolean) */ public Node importNode(Node arg0, boolean arg1) throws DOMException { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#createElementNS(java.lang.String, * java.lang.String) */ public Element createElementNS(String arg0, String arg1) throws DOMException { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#createAttributeNS(java.lang.String, * java.lang.String) */ public Attr createAttributeNS(String arg0, String arg1) throws DOMException { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#getElementsByTagNameNS(java.lang.String, * java.lang.String) */ public NodeList getElementsByTagNameNS(String arg0, String arg1) { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#getElementById(java.lang.String) */ public Element getElementById(String arg0) { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.w3c.dom.Node#getOwnerDocument() */ public org.w3c.dom.Document getOwnerDocument() { return this; } /** * Copy the document fragment starting at the specified node to the given * document builder. * * @param node * @param receiver */ public void copyTo(NodeImpl node, DocumentBuilderReceiver receiver) throws SAXException { copyTo(node, receiver, false); } protected void copyTo(NodeImpl node, DocumentBuilderReceiver receiver, boolean expandRefs) throws SAXException { NodeImpl top = node; while (node != null) { copyStartNode(node, receiver, expandRefs); NodeImpl nextNode; if (node instanceof ReferenceNode) //Nothing more to stream ? nextNode = null; else nextNode = (NodeImpl) node.getFirstChild(); while (nextNode == null) { copyEndNode(node, receiver); if (top != null && top.nodeNumber == node.nodeNumber) break; //No nextNode if the top node is a Document node // if (top != null && top.nodeNumber == 0) // break; nextNode = (NodeImpl) node.getNextSibling(); if (nextNode == null) { node = (NodeImpl) node.getParentNode(); if (node == null || (top != null && top.nodeNumber == node.nodeNumber)) { copyEndNode(node, receiver); nextNode = null; break; } } } node = nextNode; } } private void copyStartNode(NodeImpl node, DocumentBuilderReceiver receiver, boolean expandRefs) throws SAXException { int nr = node.nodeNumber; switch (node.getNodeType()) { case Node.ELEMENT_NODE: QName nodeName = document.nodeName[nr]; receiver.startElement(nodeName, null); int attr = document.alpha[nr]; if (-1 < attr) { while (attr < document.nextAttr && document.attrParent[attr] == nr) { QName attrQName = document.attrName[attr]; receiver.attribute(attrQName, attrValue[attr]); ++attr; } } int ns = document.alphaLen[nr]; if (-1 < ns) { while (ns < document.nextNamespace && document.namespaceParent[ns] == nr) { QName nsQName = document.namespaceCode[ns]; receiver.addNamespaceNode(nsQName); ++ns; } } break; case Node.TEXT_NODE: receiver.characters(document.characters, document.alpha[nr], document.alphaLen[nr]); break; case Node.CDATA_SECTION_NODE: receiver.cdataSection(document.characters, document.alpha[nr], document.alphaLen[nr]); break; case Node.ATTRIBUTE_NODE: QName attrQName = document.attrName[nr]; receiver.attribute(attrQName, attrValue[nr]); break; case Node.COMMENT_NODE: receiver.comment(document.characters, document.alpha[nr], document.alphaLen[nr]); break; case Node.PROCESSING_INSTRUCTION_NODE: QName qn = document.nodeName[nr]; String data = new String(document.characters, document.alpha[nr], document.alphaLen[nr]); receiver.processingInstruction(qn.getLocalName(), data); break; case NodeImpl.REFERENCE_NODE: if(expandRefs) { Serializer serializer = context.getBroker().getSerializer(); serializer.reset(); serializer.setProperty(Serializer.GENERATE_DOC_EVENTS, "false"); serializer.setReceiver(receiver); serializer.toReceiver(document.references[document.alpha[nr]], false, false); } else { receiver.addReferenceNode(document.references[document.alpha[nr]]); } break; } } private void copyEndNode(NodeImpl node, DocumentBuilderReceiver receiver) throws SAXException { if(node.getNodeType() == Node.ELEMENT_NODE) receiver.endElement(node.getQName()); } /** * Expand all reference nodes in the current document, i.e. replace them by * real nodes. Reference nodes are just pointers to nodes from other documents * stored in the database. The XQuery engine uses reference nodes to speed * up the creation of temporary doc fragments. * * This method creates a new copy of the document contents and expands all * reference nodes. */ public void expand() throws DOMException { if (size == 0) return; DocumentImpl newDoc = expandRefs(null); copyDocContents(newDoc); } public DocumentImpl expandRefs(NodeImpl rootNode) throws DOMException { if(nextRef == 0) { computeNodeIds(); return this; } MemTreeBuilder builder = new MemTreeBuilder(context); DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(builder); try { builder.startDocument(); NodeImpl node = rootNode == null ? (NodeImpl) getFirstChild() : rootNode; while(node != null) { copyTo(node, receiver, true); node = (NodeImpl) node.getNextSibling(); } receiver.endDocument(); } catch (SAXException e) { throw new DOMException(DOMException.INVALID_STATE_ERR, e.getMessage()); } DocumentImpl newDoc = builder.getDocument(); newDoc.computeNodeIds(); return newDoc; } public NodeImpl getNodeById(NodeId id) { expand(); for (int i = 0; i < size; i++) { if (id.equals(nodeId[i])) return getNode(i); } return null; } private void computeNodeIds() { if (nodeId[0] != null) return; nodeId[0] = context.getBroker().getBrokerPool().getNodeFactory().documentNodeId(); if (size == 1) return; NodeId nextId = context.getBroker().getBrokerPool().getNodeFactory().createInstance(); NodeImpl next = (NodeImpl) getFirstChild(); while (next != null) { computeNodeIds(nextId, next.nodeNumber); next = (NodeImpl) next.getNextSibling(); nextId = nextId.nextSibling(); } } private void computeNodeIds(NodeId id, int nodeNr) { nodeId[nodeNr] = id; if (nodeKind[nodeNr] == Node.ELEMENT_NODE) { NodeId nextId = id.newChild(); int attr = document.alpha[nodeNr]; if(-1 < attr) { while (attr < document.nextAttr && document.attrParent[attr] == nodeNr) { attrNodeId[attr] = nextId; nextId = nextId.nextSibling(); ++attr; } } int nextNode = getFirstChildFor(nodeNr); while (nextNode > nodeNr) { computeNodeIds(nextId, nextNode); nextNode = document.next[nextNode]; if (nextNode > nodeNr) nextId = nextId.nextSibling(); } } } /** * @param newDoc */ private void copyDocContents(DocumentImpl newDoc) { namePool = newDoc.namePool; nodeKind = newDoc.nodeKind; treeLevel = newDoc.treeLevel; next = newDoc.next; nodeName = newDoc.nodeName; nodeId = newDoc.nodeId; alpha = newDoc.alpha; alphaLen = newDoc.alphaLen; characters = newDoc.characters; nextChar = newDoc.nextChar; attrName = newDoc.attrName; attrNodeId = newDoc.attrNodeId; attrParent = newDoc.attrParent; attrValue = newDoc.attrValue; nextAttr = newDoc.nextAttr; namespaceParent = newDoc.namespaceParent; namespaceCode = newDoc.namespaceCode; nextNamespace = newDoc.nextNamespace; size = newDoc.size; documentRootNode = newDoc.documentRootNode; references = newDoc.references; nextRef = newDoc.nextRef; } /** * Stream the specified document fragment to a receiver. This method * is called by the serializer to output in-memory nodes. * * @param serializer * @param node * @param receiver * @throws SAXException */ public void streamTo(Serializer serializer, NodeImpl node, Receiver receiver) throws SAXException { NodeImpl top = node; while (node != null) { startNode(serializer, node, receiver); NodeImpl nextNode; if (node instanceof ReferenceNode) //Nothing more to stream ? nextNode = null; else nextNode = (NodeImpl) node.getFirstChild(); while (nextNode == null) { endNode(node, receiver); if (top != null && top.nodeNumber == node.nodeNumber) break; nextNode = (NodeImpl) node.getNextSibling(); if (nextNode == null) { node = (NodeImpl) node.getParentNode(); if (node == null || (top != null && top.nodeNumber == node.nodeNumber)) { endNode(node, receiver); nextNode = null; break; } } } node = nextNode; } } private void startNode(Serializer serializer, NodeImpl node, Receiver receiver) throws SAXException { int nr = node.nodeNumber; switch (node.getNodeType()) { case Node.ELEMENT_NODE: QName nodeName = document.nodeName[nr]; // output required namespace declarations int ns = document.alphaLen[nr]; if (-1 < ns) { while (ns < document.nextNamespace && document.namespaceParent[ns] == nr) { QName nsQName = document.namespaceCode[ns]; if ("xmlns".equals(nsQName.getLocalName())) receiver.startPrefixMapping("", nsQName.getNamespaceURI()); else receiver.startPrefixMapping(nsQName.getLocalName(), nsQName.getNamespaceURI()); ++ns; } } // create the attribute list AttrList attribs = null; int attr = document.alpha[nr]; if (-1 < attr) { attribs = new AttrList(); while (attr < document.nextAttr && document.attrParent[attr] == nr) { QName attrQName = document.attrName[attr]; attribs.addAttribute(attrQName, attrValue[attr]); ++attr; } } receiver.startElement(nodeName, attribs); break; case Node.TEXT_NODE: receiver.characters(new String(document.characters, document.alpha[nr], document.alphaLen[nr])); break; case Node.ATTRIBUTE_NODE: QName attrQName = document.attrName[nr]; receiver.attribute(attrQName, attrValue[nr]); break; case Node.COMMENT_NODE: receiver.comment(document.characters, document.alpha[nr], document.alphaLen[nr]); break; case Node.PROCESSING_INSTRUCTION_NODE: QName qn = document.nodeName[nr]; String data = new String(document.characters, document.alpha[nr], document.alphaLen[nr]); receiver.processingInstruction(qn.getLocalName(), data); break; case Node.CDATA_SECTION_NODE: receiver.cdataSection(document.characters, document.alpha[nr], document.alphaLen[nr]); break; case NodeImpl.REFERENCE_NODE: serializer.toReceiver(document.references[document.alpha[nr]], true, false); break; } } private void endNode(NodeImpl node, Receiver receiver) throws SAXException { if(node.getNodeType() == Node.ELEMENT_NODE) { receiver.endElement(node.getQName()); // end all prefix mappings used for the element int nr = node.nodeNumber; int ns = document.alphaLen[nr]; if (-1 < ns) { while (ns < document.nextNamespace && document.namespaceParent[ns] == nr) { QName nsQName = document.namespaceCode[ns]; if ("xmlns".equals(nsQName.getLocalName())) receiver.endPrefixMapping(""); else receiver.endPrefixMapping(nsQName.getLocalName()); ++ns; } } } } public org.exist.dom.DocumentImpl makePersistent() throws XPathException { if (size <= 1) return null; return context.storeTemporaryDoc(this); } public int getChildCount() { int count = 0; int top = size > 1 ? 1 : -1; while(top > 0) { ++count; top = getNextSiblingFor(top); } return count; } public boolean hasChildNodes() { return getChildCount() > 0; } public String getLocalName() { return ""; } public String getNamespaceURI() { return ""; } /** ? @see org.w3c.dom.Document#getInputEncoding() */ public String getInputEncoding() { // maybe TODO - new DOM interfaces - Java 5.0 return null; } /** ? @see org.w3c.dom.Document#getXmlEncoding() */ public String getXmlEncoding() { // maybe TODO - new DOM interfaces - Java 5.0 return null; } /** ? @see org.w3c.dom.Document#getXmlStandalone() */ public boolean getXmlStandalone() { // maybe TODO - new DOM interfaces - Java 5.0 return false; } /** ? @see org.w3c.dom.Document#setXmlStandalone(boolean) */ public void setXmlStandalone(boolean xmlStandalone) throws DOMException { // maybe TODO - new DOM interfaces - Java 5.0 } /** ? @see org.w3c.dom.Document#getXmlVersion() */ public String getXmlVersion() { // maybe TODO - new DOM interfaces - Java 5.0 return null; } /** ? @see org.w3c.dom.Document#setXmlVersion(java.lang.String) */ public void setXmlVersion(String xmlVersion) throws DOMException { // maybe TODO - new DOM interfaces - Java 5.0 } /** ? @see org.w3c.dom.Document#getStrictErrorChecking() */ public boolean getStrictErrorChecking() { // maybe TODO - new DOM interfaces - Java 5.0 return false; } /** ? @see org.w3c.dom.Document#setStrictErrorChecking(boolean) */ public void setStrictErrorChecking(boolean strictErrorChecking) { // maybe TODO - new DOM interfaces - Java 5.0 } /** ? @see org.w3c.dom.Document#getDocumentURI() */ public String getDocumentURI() { return documentURI; } /** ? @see org.w3c.dom.Document#setDocumentURI(java.lang.String) */ public void setDocumentURI(String documentURI) { this.documentURI = documentURI; } /** ? @see org.w3c.dom.Document#adoptNode(org.w3c.dom.Node) */ public Node adoptNode(Node source) throws DOMException { // maybe TODO - new DOM interfaces - Java 5.0 return null; } /** ? @see org.w3c.dom.Document#getDomConfig() */ public DOMConfiguration getDomConfig() { // maybe TODO - new DOM interfaces - Java 5.0 return null; } /** ? @see org.w3c.dom.Document#normalizeDocument() */ public void normalizeDocument() { // maybe TODO - new DOM interfaces - Java 5.0 } /** ? @see org.w3c.dom.Document#renameNode(org.w3c.dom.Node, java.lang.String, java.lang.String) */ public Node renameNode(Node n, String namespaceURI, String qualifiedName) throws DOMException { // maybe TODO - new DOM interfaces - Java 5.0 return null; } public void setContext(XQueryContext context) { this.context = context; } public XQueryContext getContext() { return context; } /** ? @see org.w3c.dom.Node#getBaseURI() */ public String getBaseURI() { if (context.isBaseURIDeclared()) { try { return context.getBaseURI() + ""; } catch (Exception e) { System.out.println("memtree/DocumentImpl::getBaseURI() exception catched: "); } } return XmldbURI.EMPTY_URI.toString(); //return XmldbURI.ROOT_COLLECTION_URI.toString(); } public int getItemType() { return Type.DOCUMENT; } public String toString() { StringBuilder result = new StringBuilder(); result.append("in-memory#"); result.append("document {"); if (size != 1) { int nodeNr = 1; while (true) { result.append(getNode(nodeNr).toString()); if (next[nodeNr] < nodeNr) { break; } else nodeNr = next[nodeNr]; } } result.append("} "); return result.toString(); } }