blob: f0d1919151d86a52860a55a72a345e57a1fb9ce2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.axiom.dom.impl.mixin;
import static org.apache.axiom.dom.DOMExceptionUtil.newDOMException;
import java.util.Hashtable;
import org.apache.axiom.core.CoreElement;
import org.apache.axiom.core.CoreModelException;
import org.apache.axiom.dom.DOMDocument;
import org.apache.axiom.dom.DOMExceptionUtil;
import org.apache.axiom.dom.DOMNode;
import org.apache.axiom.dom.DOMNodeFactory;
import org.apache.axiom.dom.DOMSemantics;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.UserDataHandler;
public aspect DOMNodeSupport {
/** Holds the user data objects */
private Hashtable DOMNode.userData; // Will be initialized in setUserData()
public final boolean DOMNode.isSupported(String feature, String version) {
return ((DOMNodeFactory)coreGetNodeFactory()).getDOMImplementation().hasFeature(feature, version);
}
public final String DOMNode.lookupNamespaceURI(String prefix) {
try {
CoreElement context = getNamespaceContext();
if (context == null) {
return null;
}
if (prefix == null) {
prefix = "";
} else if (prefix.length() == 0) {
return null;
}
String namespaceURI = context.coreLookupNamespaceURI(prefix, DOMSemantics.INSTANCE);
return namespaceURI == null || namespaceURI.length() == 0 ? null : namespaceURI;
} catch (CoreModelException ex) {
throw DOMExceptionUtil.toUncheckedException(ex);
}
}
public final String DOMNode.lookupPrefix(String namespaceURI) {
try {
CoreElement context = getNamespaceContext();
if (context == null) {
return null;
}
if (namespaceURI == null) {
return null;
} else {
String prefix = context.coreLookupPrefix(namespaceURI, DOMSemantics.INSTANCE);
return prefix == null || prefix.length() == 0 ? null : prefix;
}
} catch (CoreModelException ex) {
throw DOMExceptionUtil.toUncheckedException(ex);
}
}
public final boolean DOMNode.isDefaultNamespace(String namespaceURI) {
try {
CoreElement context = getNamespaceContext();
if (context == null) {
return false;
}
if (namespaceURI == null) {
namespaceURI = "";
}
return namespaceURI.equals(context.coreLookupNamespaceURI("", DOMSemantics.INSTANCE));
} catch (CoreModelException ex) {
throw DOMExceptionUtil.toUncheckedException(ex);
}
}
public final Node DOMNode.cloneNode(boolean deep) {
try {
DOMNode clone = (DOMNode)coreClone(deep ? DOMSemantics.DEEP_CLONE : DOMSemantics.SHALLOW_CLONE, null);
if (!(clone instanceof DOMDocument)) {
clone.coreSetOwnerDocument(coreGetOwnerDocument(true));
}
return clone;
} catch (CoreModelException ex) {
throw DOMExceptionUtil.toUncheckedException(ex);
}
}
public void DOMNode.normalize() {
//Parent node should override this
}
/*
* DOM-Level 3 methods
*/
public String DOMNode.getBaseURI() {
// TODO TODO
throw new UnsupportedOperationException("TODO");
}
public short DOMNode.compareDocumentPosition(Node other) throws DOMException {
// This is not yet implemented. In the meantime, we throw a DOMException
// and not an UnsupportedOperationException, since this works better with
// some other libraries (such as Saxon 8.9).
throw newDOMException(DOMException.NOT_SUPPORTED_ERR);
}
public boolean DOMNode.isSameNode(Node node) {
// TODO : check
return this == node;
}
/**
* Tests whether two nodes are equal. <br>This method tests for equality of nodes, not sameness
* (i.e., whether the two nodes are references to the same object) which can be tested with
* <code>Node.isSameNode()</code>. All nodes that are the same will also be equal, though the
* reverse may not be true. <br>Two nodes are equal if and only if the following conditions are
* satisfied: <ul> <li>The two nodes are of the same type. </li> <li>The following string
* attributes are equal: <code>nodeName</code>, <code>localName</code>,
* <code>namespaceURI</code>, <code>prefix</code>, <code>nodeValue</code> . This is: they are
* both <code>null</code>, or they have the same length and are character for character
* identical. </li> <li>The <code>attributes</code> <code>NamedNodeMaps</code> are equal. This
* is: they are both <code>null</code>, or they have the same length and for each node that
* exists in one map there is a node that exists in the other map and is equal, although not
* necessarily at the same index. </li> <li>The <code>childNodes</code> <code>NodeLists</code>
* are equal. This is: they are both <code>null</code>, or they have the same length and contain
* equal nodes at the same index. Note that normalization can affect equality; to avoid this,
* nodes should be normalized before being compared. </li> </ul> <br>For two
* <code>DocumentType</code> nodes to be equal, the following conditions must also be satisfied:
* <ul> <li>The following string attributes are equal: <code>publicId</code>,
* <code>systemId</code>, <code>internalSubset</code>. </li> <li>The <code>entities</code>
* <code>NamedNodeMaps</code> are equal. </li> <li>The <code>notations</code>
* <code>NamedNodeMaps</code> are equal. </li> </ul> <br>On the other hand, the following do not
* affect equality: the <code>ownerDocument</code>, <code>baseURI</code>, and
* <code>parentNode</code> attributes, the <code>specified</code> attribute for
* <code>Attr</code> nodes, the <code>schemaTypeInfo</code> attribute for <code>Attr</code> and
* <code>Element</code> nodes, the <code>Text.isElementContentWhitespace</code> attribute for
* <code>Text</code> nodes, as well as any user data or event listeners registered on the nodes.
* <p ><b>Note:</b> As a general rule, anything not mentioned in the description above is not
* significant in consideration of equality checking. Note that future versions of this
* specification may take into account more attributes and implementations conform to this
* specification are expected to be updated accordingly.
*
* @param node The node to compare equality with.
* @return Returns <code>true</code> if the nodes are equal, <code>false</code> otherwise.
* @since DOM Level 3
*/
//TODO : sumedha, complete
public boolean DOMNode.isEqualNode(Node node) {
final boolean equal = true;
final boolean notEqual = false;
if (this.getNodeType() != node.getNodeType()) {
return notEqual;
}
if (checkStringAttributeEquality(node)) {
if (checkNamedNodeMapEquality(node)) {
} else {
return notEqual;
}
} else {
return notEqual;
}
return equal;
}
private boolean DOMNode.checkStringAttributeEquality(Node node) {
final boolean equal = true;
final boolean notEqual = false;
// null not-null -> true
// not-null null -> true
// null null -> false
// not-null not-null -> false
//NodeName
if (node.getNodeName() == null ^ this.getNodeName() == null) {
return notEqual;
} else {
if (node.getNodeName() == null) {
//This means both are null.do nothing
} else {
if (!(node.getNodeName().equals(this.getNodeName()))) {
return notEqual;
}
}
}
//localName
if (node.getLocalName() == null ^ this.getLocalName() == null) {
return notEqual;
} else {
if (node.getLocalName() == null) {
//This means both are null.do nothing
} else {
if (!(node.getLocalName().equals(this.getLocalName()))) {
return notEqual;
}
}
}
//namespaceURI
if (node.getNamespaceURI() == null ^ this.getNamespaceURI() == null) {
return notEqual;
} else {
if (node.getNamespaceURI() == null) {
//This means both are null.do nothing
} else {
if (!(node.getNamespaceURI().equals(this.getNamespaceURI()))) {
return notEqual;
}
}
}
//prefix
if (node.getPrefix() == null ^ this.getPrefix() == null) {
return notEqual;
} else {
if (node.getPrefix() == null) {
//This means both are null.do nothing
} else {
if (!(node.getPrefix().equals(this.getPrefix()))) {
return notEqual;
}
}
}
//nodeValue
if (node.getNodeValue() == null ^ this.getNodeValue() == null) {
return notEqual;
} else {
if (node.getNodeValue() == null) {
//This means both are null.do nothing
} else {
if (!(node.getNodeValue().equals(this.getNodeValue()))) {
return notEqual;
}
}
}
return equal;
}
private boolean DOMNode.checkNamedNodeMapEquality(Node node) {
final boolean equal = true;
final boolean notEqual = false;
if (node.getAttributes() == null ^ this.getAttributes() == null) {
return notEqual;
}
NamedNodeMap thisNamedNodeMap = this.getAttributes();
NamedNodeMap nodeNamedNodeMap = node.getAttributes();
// null not-null -> true
// not-null null -> true
// null null -> false
// not-null not-null -> false
if (thisNamedNodeMap == null ^ nodeNamedNodeMap == null) {
return notEqual;
} else {
if (thisNamedNodeMap == null) {
//This means both are null.do nothing
} else {
if (thisNamedNodeMap.getLength() != nodeNamedNodeMap.getLength()) {
return notEqual;
} else {
//they have the same length and for each node that exists in one map
//there is a node that exists in the other map and is equal, although
//not necessarily at the same index.
int itemCount = thisNamedNodeMap.getLength();
for (int a = 0; a < itemCount; a++) {
DOMNode thisNode = (DOMNode) thisNamedNodeMap.item(a);
DOMNode tmpNode =
(DOMNode) nodeNamedNodeMap.getNamedItem(thisNode.getNodeName());
if (tmpNode == null) {
//i.e. no corresponding node
return notEqual;
} else {
if (!(thisNode.isEqualNode(tmpNode))) {
return notEqual;
}
}
}
}
}
}
return equal;
}
public Object DOMNode.getFeature(String feature, String version) {
// TODO TODO
throw new UnsupportedOperationException("TODO");
}
/* *
* userData storage/hashtable will be called only when the user needs to set user data. Previously, it was done as,
* for every node a new Hashtable created making the excution very inefficient. According to profiles, no. of method
* invocations to setUserData() method is very low, so this implementation is better.
* Another option:
* TODO do a profile and check the times for hashtable initialization. If it's still higher, we have to go to second option
* Create a separate class(UserData) to store key and value pairs. Then put those objects to a array with a reasonable size.
* then grow it accordingly. @ Kasun Gajasinghe
* @param key userData key
* @param value userData value
* @param userDataHandler it seems all invocations sends null for this parameter.
* Kept it for the moment just for being on the safe side.
* @return previous Object if one is set before.
*/
public Object DOMNode.setUserData(String key, Object value, UserDataHandler userDataHandler) {
if (userData == null) {
userData = new Hashtable();
}
return userData.put(key, value);
}
public Object DOMNode.getUserData(String key) {
if (userData != null) {
return userData.get(key);
}
return null;
}
/**
* Get the owner document of this node. In contrast to {@link Node#getOwnerDocument()}, this
* method returns a non null value when invoked on a {@link Document} instance.
*
* @return the owner document
*/
final DOMDocument DOMNode.ownerDocument() {
return (DOMDocument)coreGetOwnerDocument(true);
}
}