com.marklogic.dom.NodeImpl Maven / Gradle / Ivy
/*
* Copyright 2003-2019 MarkLogic Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.marklogic.dom;
import java.util.ArrayList;
import java.util.Stack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.UserDataHandler;
import com.marklogic.tree.ExpandedTree;
import com.marklogic.tree.NodeKind;
/**
* A read-only W3C DOM Node implementation of MarkLogic's internal
* representation of a node as stored in the expanded tree cache of a forest on
* disk.
*
*
* This interface is effectively read-only: Setters and update methods inherited
* from org.w3c.Node
are not supported and will raise an exception
* if called.
*
*
* @author jchen
*/
public abstract class NodeImpl implements Node {
public static final Log LOG = LogFactory.getLog(NodeImpl.class);
private static final NodeList emptyNodeList = new NodeList() {
public int getLength() {
return 0;
}
public Node item(int index) {
return null;
}
};
protected final ExpandedTree tree;
protected final int node;
/**
* No public constructor; only subclasses of Node should be instantiated
*/
NodeImpl(ExpandedTree tree, int node) {
this.tree = tree;
this.node = node;
}
public ExpandedTree getExpandedTree() {
return tree;
}
/** Unsupported. */
public Node appendChild(Node newChild) throws DOMException {
throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, null);
}
/**
* CloneNode is only supported for document node.
*
* {@inheritDoc}
*
*/
public Node cloneNode(boolean deep) {
throw new UnsupportedOperationException();
}
/**
* Given a owner document, clone the node
*
* @param doc
* owner document
* @param deep
* the flag to indicate deep clone or not
* @return null
*/
protected Node cloneNode(Document doc, boolean deep) {
return null;
}
/**
* {@inheritDoc}
*/
public short compareDocumentPosition(Node other) throws DOMException {
if (other instanceof NodeImpl) {
NodeImpl otherNode = (NodeImpl) other;
if (this.tree == otherNode.tree) {
if (tree.nodeOrdinal[node] > tree.nodeOrdinal[otherNode.node]) {
int ancestor = tree.nodeParentNodeRepID[node];
while (ancestor != Integer.MAX_VALUE
&& tree.nodeOrdinal[ancestor] >= tree.nodeOrdinal[otherNode.node]) {
if (ancestor == otherNode.node)
return DOCUMENT_POSITION_CONTAINS
| DOCUMENT_POSITION_PRECEDING;
ancestor = tree.nodeParentNodeRepID[ancestor];
}
return DOCUMENT_POSITION_PRECEDING;
} else {
int ancestor = tree.nodeParentNodeRepID[otherNode.node];
while (ancestor != Integer.MAX_VALUE
&& tree.nodeOrdinal[ancestor] <= tree.nodeOrdinal[otherNode.node]) {
if (ancestor == node)
return DOCUMENT_POSITION_CONTAINED_BY
| DOCUMENT_POSITION_FOLLOWING;
ancestor = tree.nodeParentNodeRepID[ancestor];
}
return DOCUMENT_POSITION_FOLLOWING;
}
} else {
return DOCUMENT_POSITION_DISCONNECTED;
}
} else {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR, null);
}
}
/** {@inheritDoc} */
public NamedNodeMap getAttributes() {
return null;
}
/** {@inheritDoc} */
public String getBaseURI() {
return tree.getDocumentURI();
}
/** {@inheritDoc} */
public NodeList getChildNodes() {
return emptyNodeList;
}
/** Unsupported. */
public Object getFeature(String feature, String version) {
assert (false);
return this;
}
/** {@inheritDoc} */
public Node getFirstChild() {
return null;
}
/** {@inheritDoc} */
public Node getLastChild() {
return null;
}
/** {@inheritDoc} */
public String getLocalName() {
return null;
}
/** {@inheritDoc} */
public String getNamespaceURI() {
return null;
}
protected Node getNextChild(int node) {
return null;
}
/** {@inheritDoc} */
public Node getNextSibling() {
NodeImpl p = (NodeImpl) getParentNode();
return (p == null ? null : p.getNextChild(node));
}
/** {@inheritDoc} */
public abstract String getNodeName();
/** {@inheritDoc} */
public short getNodeType() {
return NodeKind.domType(tree.nodeKind[node]);
}
/** {@inheritDoc} */
public String getNodeValue() throws DOMException {
return null; // overridden in some subclasses
}
/**
* {@inheritDoc}
*
* OwnerDocument of namespace attribute is created artificially,
* which only contains the attribute only.
*/
public Document getOwnerDocument() {
return (DocumentImpl) (this.tree.node(0));
}
/** {@inheritDoc} */
public Node getParentNode() {
// assume no linkNodeKind
return tree.node(tree.nodeParentNodeRepID[node]);
}
protected int getPrefixID(int uriAtom) {
return -1;
}
/** {@inheritDoc} */
public String getPrefix() {
return null;
}
protected Node getPreviousChild(int child) {
return null;
}
public Node getPreviousSibling() {
NodeImpl p = (NodeImpl) getParentNode();
return (p == null ? null : p.getPreviousChild(node));
}
// visit every child node, excluding COMMENT_NODE and
// PROCESSING_INSTRUCTION_NODE nodes.
private boolean hasTextContent(Node child) {
return child.getNodeType() != Node.COMMENT_NODE
&& child.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE;
}
/** {@inheritDoc}
*
* Overwritten by TextImpl, CommentImpl and ProcessingInstructionImpl
*/
public String getTextContent() throws DOMException {
StringBuilder sb = new StringBuilder();
getTextContent(sb);
return sb.toString();
}
// internal method taking a StringBuffer in parameter
private void getTextContent(StringBuilder sb) throws DOMException {
NodeList children = getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (hasTextContent(child)) {
sb.append(child.getTextContent());
}
}
}
/** Unsupported. */
public Object getUserData(String key) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR, null);
}
/** {@inheritDoc} */
public boolean hasAttributes() {
return false;
}
/** {@inheritDoc} */
public boolean hasChildNodes() {
return false;
}
/** Unsupported. */
public Node insertBefore(Node newChild, Node refChild) throws DOMException {
throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, null);
}
/**
* {@inheritDoc}
*
* Not supported for namespace declaration. Overrided by DocumentImpl and
* ElementImpl
*
*/
public boolean isDefaultNamespace(String namespaceURI) {
int type = getNodeType();
if (type == NodeKind.ATTR) {
if (this instanceof AttrImpl == false) {
// ns decl
throw new UnsupportedOperationException();
}
}
Node p = getParentNode();
return p.isDefaultNamespace(namespaceURI);
}
private boolean notequals(String a, String b){
if (a==null) return (b!=null);
return !a.equals(b);
}
/** {@inheritDoc} */
public boolean isEqualNode(Node other) {
// Note that normalization can affect equality; to avoid this,
// nodes should be normalized before being compared.
// For the moment, normalization cannot be done.
if (other==null) return false;
if (getNodeType() != other.getNodeType())
return false;
if (!getLocalName().equals(other.getLocalName()))
return false;
if (notequals(getNamespaceURI(),other.getNamespaceURI()))
return false;
if (notequals(getPrefix(), other.getPrefix()))
return false;
if (notequals(getNodeValue(),other.getNodeValue()))
return false;
if (hasChildNodes() != other.hasChildNodes())
return false;
if (hasAttributes() != other.hasAttributes())
return false;
if (hasChildNodes()) {
NamedNodeMap thisAttr = getAttributes();
NamedNodeMap otherAttr = other.getAttributes();
if (thisAttr.getLength() != otherAttr.getLength())
return false;
for (int i = 0; i < thisAttr.getLength(); i++)
if (thisAttr.item(i).isEqualNode(otherAttr.item(i)))
return false;
}
if (hasAttributes()) {
NodeList thisChild = getChildNodes();
NodeList otherChild = other.getChildNodes();
if (thisChild.getLength() != otherChild.getLength())
return false;
for (int i = 0; i < thisChild.getLength(); i++)
if (thisChild.item(i).isEqualNode(otherChild.item(i)))
return false;
}
return true;
}
/** {@inheritDoc} */
public boolean isSameNode(Node other) {
return (other instanceof NodeImpl)
&& (((NodeImpl) other).tree == tree)
&& (((NodeImpl) other).node == node);
}
// TODO: Consider implementing Traversal
// TODO - override in subclasses?
public boolean isSupported(String feature, String version) {
if (feature.equalsIgnoreCase("Core"))
return true;
if (feature.equalsIgnoreCase("XML"))
return true;
return false;
}
protected int getNSNodeID(long ordinal, long minOrdinal) {
if (ordinal < minOrdinal)
return -1;
int idx = getNSNodeID(ordinal);
if (idx >= 0 && tree.nsNodeOrdinal[idx] >= minOrdinal)
return idx;
return -1;
}
protected int getNSNodeID(long ordinal) {
int R = tree.numNSNodeReps;
if (R == 0)
return -1;
int L = 0;
while (L + 1 < R) {
int M = (L + R) >>> 1;
if (ordinal < tree.nsNodeOrdinal[M])
R = M;
else
L = M;
}
if (ordinal < tree.nsNodeOrdinal[L])
--L;
for (;;) {
if (L == -1)
break;
if (tree.nsNodePrefixAtom[L] != -1)
break;
L = tree.nsNodePrevNSNodeRepID[L];
}
return L;
}
protected int nextNSNodeID(int ns, long minOrdinal) {
for (;;) {
ns = tree.nsNodePrevNSNodeRepID[ns];
if (ns == -1)
return -1;
if (tree.nsNodePrefixAtom[ns] != -1)
break;
}
if (tree.nsNodeOrdinal[ns] < minOrdinal)
ns = -1;
return ns;
}
/** {@inheritDoc} */
// http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespacePrefixAlgo
public String lookupNamespaceURI(String prefix) {
return null;
}
/** {@inheritDoc} */
public String lookupPrefix(String namespaceURI) {
return null;
}
/** Unsupported. */
public void normalize() {
throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, null);
}
/** Unsupported. */
public Node removeChild(Node oldChild) throws DOMException {
throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, null);
}
/** Unsupported. */
public Node replaceChild(Node newChild, Node oldChild) throws DOMException {
throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, null);
}
/** Unsupported. */
public void setNodeValue(String nodeValue) throws DOMException {
throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, null);
}
/** Unsupported. */
public void setPrefix(String prefix) throws DOMException {
throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, null);
}
/** Unsupported. */
public void setTextContent(String textContent) throws DOMException {
throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, null);
}
/** Unsupported. */
public Object setUserData(String key, Object data, UserDataHandler handler) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR, null);
}
protected NodeList getElementsByTagNameNSOrNodeName(String namespaceURI, String name,final boolean nodeName) {
final String tagname = name;
final String ns = namespaceURI;
final Node thisNode = this;
return new NodeList() {
protected ArrayList elementList = new ArrayList();
protected boolean done = false;
protected void init() {
if (done) return;
Stack childrenStack = new Stack();
childrenStack.push(thisNode);
boolean root = true;
while ( !childrenStack.isEmpty()) {
Node curr = childrenStack.pop();
NodeList children = curr.getChildNodes();
for (int childi = children.getLength() -1 ; childi >=0; childi--)
if (children.item(childi).getNodeType() == Node.ELEMENT_NODE)
childrenStack.push(children.item(childi));
if (root) {
root = false;
continue;
}
if (nodeName) {
if (curr.getNodeName().equals(tagname) || tagname.equals("*"))
elementList.add(curr);
}
else {
// do nothing if only one of the two is null
if ("*".equals(ns) && "*".equals(tagname)){
elementList.add(curr); continue;
}
if (ns != null) {
if ((ns.equals("*") || ns.equals(curr.getNamespaceURI())) &&
(tagname.equals("*") || tagname.equals(curr.getLocalName())))
elementList.add(curr);
}
else if (tagname.equals("*") || tagname.equals(curr.getLocalName()))
elementList.add(curr);
}
}
done = true;
}
public int getLength() {
init();
return elementList.size();
}
public Node item(int index) {
init();
return (index < getLength()) ? elementList.get(index) : null;
}
};
}
protected String builtinNSPrefix(String URI) {
if (URI == null)
return null;
if (URI.equals("http://www.w3.org/XML/1998/namespace"))
return "xml";
if (URI.equals("http://www.w3.org/2000/xmlns/"))
return "xmlns";
if (URI.equals("http://www.w3.org/2001/XMLSchema"))
return "xs";
if (URI.equals("http://www.w3.org/2001/XMLSchema-instance"))
return "xsi";
if (URI.equals("http://www.w3.org/2003/05/xpath-datatypes"))
return "xdt";
if (URI.equals("http://marklogic.com/xdmp"))
return "xdmp";
if (URI.equals("http://marklogic.com/xqe"))
return "xqe";
if (URI.equals("http://marklogic.com/xdmp/security"))
return "sec";
if (URI.equals("http://www.w3.org/2005/xqt-errors"))
return "err";
if (URI.equals("http://marklogic.com/xdmp/error"))
return "error";
if (URI.equals("http://marklogic.com/xdmp/directory"))
return "dir";
if (URI.equals("DAV:"))
return "dav";
if (URI.equals("http://marklogic.com/xdmp/lock"))
return "lock";
if (URI.equals("http://marklogic.com/xdmp/property"))
return "prop";
return null;
}
}