blob: 57e5236d24ceaa2ff0d0e86ef1f0ac8a27d35815 [file] [log] [blame]
/*****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
*****************************************************************************/
package org.apache.batik.dom;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import org.apache.batik.dom.util.DOMUtilities;
import org.apache.batik.dom.util.HashTable;
import org.w3c.dom.Attr;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.DocumentEvent;
import org.w3c.dom.events.MutationEvent;
/**
* This class implements the {@link org.w3c.dom.Element} interface.
*
* @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
* @version $Id$
*/
public abstract class AbstractElement
extends AbstractParentChildNode
implements Element {
/**
* The attributes of this element.
*/
protected NamedNodeMap attributes;
/**
* Creates a new AbstractElement object.
*/
protected AbstractElement() {
}
/**
* Creates a new AbstractElement object.
* @param name The element name for validation purposes.
* @param owner The owner document.
* @exception DOMException
* INVALID_CHARACTER_ERR: if name contains invalid characters,
*/
protected AbstractElement(String name, AbstractDocument owner) {
ownerDocument = owner;
if (!DOMUtilities.isValidName(name)) {
throw createDOMException(DOMException.INVALID_CHARACTER_ERR,
"xml.name",
new Object[] { name });
}
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Node#getNodeType()}.
* @return {@link org.w3c.dom.Node#ELEMENT_NODE}
*/
public short getNodeType() {
return ELEMENT_NODE;
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Node#hasAttributes()}.
*/
public boolean hasAttributes() {
return attributes != null && attributes.getLength() != 0;
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Node#getAttributes()}.
*/
public NamedNodeMap getAttributes() {
return (attributes == null)
? attributes = createAttributes()
: attributes;
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Element#getTagName()}.
* @return {@link #getNodeName()}.
*/
public String getTagName() {
return getNodeName();
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Element#hasAttribute(String)}.
*/
public boolean hasAttribute(String name) {
return attributes != null && attributes.getNamedItem(name) != null;
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Element#getAttribute(String)}.
*/
public String getAttribute(String name) {
if (attributes == null) {
return "";
}
Attr attr = (Attr)attributes.getNamedItem(name);
return (attr == null) ? "" : attr.getValue();
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#setAttribute(String,String)}.
*/
public void setAttribute(String name, String value) throws DOMException {
if (attributes == null) {
attributes = createAttributes();
}
Attr attr = getAttributeNode(name);
if (attr == null) {
attr = getOwnerDocument().createAttribute(name);
attr.setValue(value);
attributes.setNamedItem(attr);
} else {
attr.setValue(value);
}
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#removeAttribute(String)}.
*/
public void removeAttribute(String name) throws DOMException {
if (attributes == null) {
throw createDOMException(DOMException.NOT_FOUND_ERR,
"attribute.missing",
new Object[] { name });
}
attributes.removeNamedItem(name);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#getAttributeNode(String)}.
*/
public Attr getAttributeNode(String name) {
if (attributes == null) {
return null;
}
return (Attr)attributes.getNamedItem(name);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#setAttributeNode(Attr)}.
*/
public Attr setAttributeNode(Attr newAttr) throws DOMException {
if (newAttr == null) {
return null;
}
if (attributes == null) {
attributes = createAttributes();
}
return (Attr)attributes.setNamedItemNS(newAttr);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#removeAttributeNode(Attr)}.
*/
public Attr removeAttributeNode(Attr oldAttr) throws DOMException {
if (oldAttr == null) {
return null;
}
if (attributes == null) {
throw createDOMException(DOMException.NOT_FOUND_ERR,
"attribute.missing",
new Object[] { oldAttr.getName() });
}
return (Attr)attributes.removeNamedItem(oldAttr.getNodeName());
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.Node#normalize()}.
*/
public void normalize() {
super.normalize();
if (attributes != null) {
NamedNodeMap map = getAttributes();
for (int i = map.getLength() - 1; i >= 0; i--) {
map.item(i).normalize();
}
}
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#hasAttributeNS(String,String)}.
*/
public boolean hasAttributeNS(String namespaceURI, String localName) {
return attributes != null &&
attributes.getNamedItemNS(namespaceURI, localName) != null;
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#getAttributeNS(String,String)}.
*/
public String getAttributeNS(String namespaceURI, String localName) {
if (attributes == null) {
return "";
}
Attr attr = (Attr)attributes.getNamedItemNS(namespaceURI, localName);
return (attr == null) ? "" : attr.getValue();
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#setAttributeNS(String,String,String)}.
*/
public void setAttributeNS(String namespaceURI,
String qualifiedName,
String value) throws DOMException {
if (attributes == null) {
attributes = createAttributes();
}
Attr attr = getAttributeNodeNS(namespaceURI, qualifiedName);
if (attr == null) {
attr = getOwnerDocument().createAttributeNS(namespaceURI,
qualifiedName);
attr.setValue(value);
attributes.setNamedItemNS(attr);
} else {
String s = attr.getValue();
attr.setValue(value);
fireDOMAttrModifiedEvent(qualifiedName,
attr,
s,
value,
MutationEvent.MODIFICATION);
}
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#removeAttributeNS(String,String)}.
*/
public void removeAttributeNS(String namespaceURI,
String localName) throws DOMException {
if (attributes == null) {
throw createDOMException(DOMException.NOT_FOUND_ERR,
"attribute.missing",
new Object[] { localName });
}
attributes.removeNamedItemNS(namespaceURI, localName);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#getAttributeNodeNS(String,String)}.
*/
public Attr getAttributeNodeNS(String namespaceURI,
String localName) {
if (attributes == null) {
return null;
}
return (Attr)attributes.getNamedItemNS(namespaceURI, localName);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.Element#setAttributeNodeNS(Attr)}.
*/
public Attr setAttributeNodeNS(Attr newAttr) throws DOMException {
if (newAttr == null) {
return null;
}
if (attributes == null) {
attributes = createAttributes();
}
return (Attr)attributes.setNamedItemNS(newAttr);
}
/**
* Called when a child node has been added.
*/
protected void nodeAdded(Node node) {
invalidateElementsByTagName(node);
}
/**
* Called when a child node is going to be removed.
*/
protected void nodeToBeRemoved(Node node) {
invalidateElementsByTagName(node);
}
/**
* Invalidates the ElementsByTagName objects of this node and its parents.
*/
private void invalidateElementsByTagName(Node node) {
if (node.getNodeType() != ELEMENT_NODE) {
return;
}
AbstractDocument ad = getCurrentDocument();
String ns = node.getNamespaceURI();
String ln = (ns == null) ? node.getNodeName() : node.getLocalName();
for (Node n = this; n != null; n = n.getParentNode()) {
switch (n.getNodeType()) {
case ELEMENT_NODE:
case DOCUMENT_NODE:
ElementsByTagName l = ad.getElementsByTagName(n, ns, ln);
if (l != null) {
l.invalidate();
}
l = ad.getElementsByTagName(n, "*", ln);
if (l != null) {
l.invalidate();
}
l = ad.getElementsByTagName(n, ns, "*");
if (l != null) {
l.invalidate();
}
l = ad.getElementsByTagName(n, "*", "*");
if (l != null) {
l.invalidate();
}
}
}
}
/**
* Creates the attribute list.
*/
protected NamedNodeMap createAttributes() {
return new NamedNodeHashMap();
}
/**
* Exports this node to the given document.
* @param n The clone node.
* @param d The destination document.
*/
protected Node export(Node n, AbstractDocument d) {
super.export(n, d);
AbstractElement ae = (AbstractElement)n;
if (attributes != null) {
NamedNodeMap map = attributes;
for (int i = map.getLength() - 1; i >= 0; i--) {
AbstractAttr aa = (AbstractAttr)map.item(i);
if (aa.getSpecified()) {
Attr attr = (Attr)aa.deepExport(aa.cloneNode(false), d);
if (aa instanceof AbstractAttrNS) {
ae.setAttributeNodeNS(attr);
} else {
ae.setAttributeNode(attr);
}
}
}
}
return n;
}
/**
* Deeply exports this node to the given document.
* @param n The clone node.
* @param d The destination document.
*/
protected Node deepExport(Node n, AbstractDocument d) {
super.deepExport(n, d);
AbstractElement ae = (AbstractElement)n;
if (attributes != null) {
NamedNodeMap map = attributes;
for (int i = map.getLength() - 1; i >= 0; i--) {
AbstractAttr aa = (AbstractAttr)map.item(i);
if (aa.getSpecified()) {
Attr attr = (Attr)aa.deepExport(aa.cloneNode(false), d);
if (aa instanceof AbstractAttrNS) {
ae.setAttributeNodeNS(attr);
} else {
ae.setAttributeNode(attr);
}
}
}
}
return n;
}
/**
* Copy the fields of the current node into the given node.
* @param n a node of the type of this.
*/
protected Node copyInto(Node n) {
super.copyInto(n);
AbstractElement ae = (AbstractElement)n;
if (attributes != null) {
NamedNodeMap map = attributes;
for (int i = map.getLength() - 1; i >= 0; i--) {
AbstractAttr aa = (AbstractAttr)map.item(i).cloneNode(false);
if (aa instanceof AbstractAttrNS) {
ae.setAttributeNodeNS(aa);
} else {
ae.setAttributeNode(aa);
}
}
}
return n;
}
/**
* Deeply copy the fields of the current node into the given node.
* @param n a node of the type of this.
*/
protected Node deepCopyInto(Node n) {
super.deepCopyInto(n);
AbstractElement ae = (AbstractElement)n;
if (attributes != null) {
NamedNodeMap map = attributes;
for (int i = map.getLength() - 1; i >= 0; i--) {
AbstractAttr aa = (AbstractAttr)map.item(i).cloneNode(true);
if (aa instanceof AbstractAttrNS) {
ae.setAttributeNodeNS(aa);
} else {
ae.setAttributeNode(aa);
}
}
}
return n;
}
/**
* Checks the validity of a node to be inserted.
* @param n The node to be inserted.
*/
protected void checkChildType(Node n) {
switch (n.getNodeType()) {
case ELEMENT_NODE:
case PROCESSING_INSTRUCTION_NODE:
case COMMENT_NODE:
case TEXT_NODE:
case CDATA_SECTION_NODE:
case ENTITY_REFERENCE_NODE:
case DOCUMENT_FRAGMENT_NODE:
break;
default:
throw createDOMException
(DOMException.HIERARCHY_REQUEST_ERR,
"child.type",
new Object[] { new Integer(getNodeType()),
getNodeName(),
new Integer(n.getNodeType()),
n.getNodeName() });
}
}
/**
* Fires a DOMAttrModified event.
* <!> WARNING: public accessor because of compilation problems
* on Solaris. Do not change.
*
* @param name The attribute's name.
* @param node The attribute's node.
* @param oldv The old value of the attribute.
* @param newv The new value of the attribute.
* @param change The modification type.
*/
public void fireDOMAttrModifiedEvent(String name, Attr node, String oldv,
String newv, short change) {
switch (change) {
case MutationEvent.ADDITION:
attrAdded(node, newv);
break;
case MutationEvent.MODIFICATION:
attrModified(node, oldv, newv);
break;
default: // MutationEvent.REMOVAL:
attrRemoved(node, oldv);
}
AbstractDocument doc = getCurrentDocument();
if (doc.getEventsEnabled() && !oldv.equals(newv)) {
DocumentEvent de = (DocumentEvent)doc;
MutationEvent ev = (MutationEvent)de.createEvent("MutationEvents");
ev.initMutationEvent("DOMAttrModified",
true, // canBubbleArg
false, // cancelableArg
node, // relatedNodeArg
oldv, // prevValueArg
newv, // newValueArg
name, // attrNameArg
change); // attrChange
dispatchEvent(ev);
}
}
/**
* Called when an attribute has been added.
*/
protected void attrAdded(Attr node, String newv) {
}
/**
* Called when an attribute has been modified.
*/
protected void attrModified(Attr node, String oldv, String newv) {
}
/**
* Called when an attribute has been removed.
*/
protected void attrRemoved(Attr node, String oldv) {
}
/**
* An implementation of the {@link org.w3c.dom.NamedNodeMap}.
*/
public class NamedNodeHashMap implements NamedNodeMap, Serializable {
/**
* The initial capacity
*/
protected final static int INITIAL_CAPACITY = 3;
/**
* The underlying array
*/
protected Entry[] table;
/**
* The number of entries
*/
protected int count;
/**
* Creates a new NamedNodeHashMap object.
*/
public NamedNodeHashMap() {
table = new Entry[INITIAL_CAPACITY];
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.NamedNodeMap#getNamedItem(String)}.
*/
public Node getNamedItem(String name) {
if (name == null) {
return null;
}
return get(null, name);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.NamedNodeMap#setNamedItem(Node)}.
*/
public Node setNamedItem(Node arg) throws DOMException {
if (arg == null) {
return null;
}
checkNode(arg);
return setNamedItem(null, arg.getNodeName(), arg);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.NamedNodeMap#removeNamedItem(String)}.
*/
public Node removeNamedItem(String name) throws DOMException {
return removeNamedItemNS(null, name);
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.NamedNodeMap#item(int)}.
*/
public Node item(int index) {
if (index < 0 || index >= count) {
return null;
}
int j = 0;
for (int i = 0; i < table.length; i++) {
Entry e = table[i];
if (e == null) {
continue;
}
do {
if (j++ == index) {
return e.value;
}
e = e.next;
} while (e != null);
}
return null;
}
/**
* <b>DOM</b>: Implements {@link org.w3c.dom.NamedNodeMap#getLength()}.
*/
public int getLength() {
return count;
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.NamedNodeMap#getNamedItemNS(String,String)}.
*/
public Node getNamedItemNS(String namespaceURI, String localName) {
return get(namespaceURI, localName);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.NamedNodeMap#setNamedItemNS(Node)}.
*/
public Node setNamedItemNS(Node arg) throws DOMException {
if (arg == null) {
return null;
}
String nsURI = arg.getNamespaceURI();
return setNamedItem(nsURI,
(nsURI == null)
? arg.getNodeName()
: arg.getLocalName(), arg);
}
/**
* <b>DOM</b>: Implements {@link
* org.w3c.dom.NamedNodeMap#removeNamedItemNS(String,String)}.
*/
public Node removeNamedItemNS(String namespaceURI, String localName)
throws DOMException {
if (isReadonly()) {
throw createDOMException
(DOMException.NO_MODIFICATION_ALLOWED_ERR,
"readonly.node.map",
new Object[] {});
}
if (localName == null) {
throw createDOMException(DOMException.NOT_FOUND_ERR,
"attribute.missing",
new Object[] { "" });
}
AbstractAttr n = (AbstractAttr)remove(namespaceURI, localName);
if (n == null) {
throw createDOMException(DOMException.NOT_FOUND_ERR,
"attribute.missing",
new Object[] { localName });
}
n.setOwnerElement(null);
// Mutation event
fireDOMAttrModifiedEvent(n.getNodeName(), n, n.getNodeValue(), "",
MutationEvent.REMOVAL);
return n;
}
/**
* Adds a node to the map.
*/
public Node setNamedItem(String ns, String name, Node arg) throws DOMException {
((AbstractAttr)arg).setOwnerElement(AbstractElement.this);
AbstractAttr result = (AbstractAttr)put(ns, name, arg);
if (result != null) {
result.setOwnerElement(null);
fireDOMAttrModifiedEvent(name,
result,
result.getNodeValue(),
"",
MutationEvent.REMOVAL);
}
fireDOMAttrModifiedEvent(name,
(Attr)arg,
"",
arg.getNodeValue(),
MutationEvent.ADDITION);
return result;
}
/**
* Checks the validity of a node to add.
*/
protected void checkNode(Node arg) {
if (isReadonly()) {
throw createDOMException
(DOMException.NO_MODIFICATION_ALLOWED_ERR,
"readonly.node.map",
new Object[] {});
}
if (getOwnerDocument() != arg.getOwnerDocument()) {
throw createDOMException(DOMException.WRONG_DOCUMENT_ERR,
"node.from.wrong.document",
new Object[] { new Integer
(arg.getNodeType()),
arg.getNodeName() });
}
if (arg.getNodeType() == ATTRIBUTE_NODE &&
((Attr)arg).getOwnerElement() != null) {
throw createDOMException(DOMException.WRONG_DOCUMENT_ERR,
"inuse.attribute",
new Object[] { arg.getNodeName() });
}
}
/**
* Gets the value of a variable
* @return the value or null
*/
protected Node get(String ns, String nm) {
int hash = hashCode(ns, nm) & 0x7FFFFFFF;
int index = hash % table.length;
for (Entry e = table[index]; e != null; e = e.next) {
if ((e.hash == hash) && e.match(ns, nm)) {
return e.value;
}
}
return null;
}
/**
* Sets a new value for the given variable
* @return the old value or null
*/
protected Node put(String ns, String nm, Node value) {
int hash = hashCode(ns, nm) & 0x7FFFFFFF;
int index = hash % table.length;
for (Entry e = table[index]; e != null; e = e.next) {
if ((e.hash == hash) && e.match(ns, nm)) {
Node old = e.value;
e.value = value;
return old;
}
}
// The key is not in the hash table
int len = table.length;
if (count++ >= (len * 3) >>> 2) {
rehash();
index = hash % table.length;
}
Entry e = new Entry(hash, ns, nm, value, table[index]);
table[index] = e;
return null;
}
/**
* Removes an entry from the table.
* @return the value or null.
*/
protected Node remove(String ns, String nm) {
int hash = hashCode(ns, nm) & 0x7FFFFFFF;
int index = hash % table.length;
Entry p = null;
for (Entry e = table[index]; e != null; e = e.next) {
if ((e.hash == hash) && e.match(ns, nm)) {
Node result = e.value;
if (p == null) {
table[index] = e.next;
} else {
p.next = e.next;
}
count--;
return result;
}
p = e;
}
return null;
}
/**
* Rehash the table
*/
protected void rehash () {
Entry[] oldTable = table;
table = new Entry[oldTable.length * 2 + 1];
for (int i = oldTable.length-1; i >= 0; i--) {
for (Entry old = oldTable[i]; old != null;) {
Entry e = old;
old = old.next;
int index = e.hash % table.length;
e.next = table[index];
table[index] = e;
}
}
}
/**
* Computes a hash code corresponding to the given strings.
*/
protected int hashCode(String ns, String nm) {
int result = (ns == null) ? 0 : ns.hashCode();
return result ^ nm.hashCode();
}
}
/**
* To manage collisions in the attributes map.
*/
protected static class Entry implements Serializable {
/**
* The hash code
*/
public int hash;
/**
* The namespace URI
*/
public String namespaceURI;
/**
* The node name.
*/
public String name;
/**
* The value
*/
public Node value;
/**
* The next entry
*/
public Entry next;
/**
* Creates a new entry
*/
public Entry(int hash, String ns, String nm, Node value, Entry next) {
this.hash = hash;
this.namespaceURI = ns;
this.name = nm;
this.value = value;
this.next = next;
}
/**
* Whether this entry match the given keys.
*/
public boolean match(String ns, String nm) {
if (namespaceURI != null) {
if (!namespaceURI.equals(ns)) {
return false;
}
} else if (ns != null) {
return false;
}
return name.equals(nm);
}
}
}