/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999-2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xerces" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999, International
* Business Machines, Inc., http://www.apache.org. For more
* information on the Apache Software Foundation, please see
*
* Only one Node may be stored per name; attempting to * store another will replace the previous value. *
* NOTE: The "primary" storage key is taken from the NodeName attribute of the * node. The "secondary" storage key is the namespaceURI and localName, when * accessed by DOM level 2 nodes. All nodes, even DOM Level 2 nodes are stored * in a single Vector sorted by the primary "nodename" key. *
* NOTE: item()'s integer index does _not_ imply that the named nodes * must be stored in an array; that's only an access method. Note too * that these indices are "live"; if someone changes the map's * contents, the indices associated with nodes may change. *
* * @version $Id: NamedNodeMapImpl.java,v 1.35 2003/01/23 23:23:25 lmartin Exp $ * @since PR-DOM-Level-1-19980818. */ public class NamedNodeMapImpl implements NamedNodeMap, Serializable { // // Constants // /** Serialization version. */ static final long serialVersionUID = -7039242451046758020L; // // Data // protected short flags; protected final static short READONLY = 0x1<<0; protected final static short CHANGED = 0x1<<1; protected final static short HASDEFAULTS = 0x1<<2; /** Nodes. */ protected Vector nodes; protected NodeImpl ownerNode; // the node this map belongs to // // Constructors // /** Constructs a named node map. */ protected NamedNodeMapImpl(NodeImpl ownerNode) { this.ownerNode = ownerNode; } // // NamedNodeMap methods // /** * Report how many nodes are currently stored in this NamedNodeMap. * Caveat: This is a count rather than an index, so the * highest-numbered node at any time can be accessed via * item(getLength()-1). */ public int getLength() { return (nodes != null) ? nodes.size() : 0; } /** * Retrieve an item from the map by 0-based index. * * @param index Which item to retrieve. Note that indices are just an * enumeration of the current contents; they aren't guaranteed to be * stable, nor do they imply any promises about the order of the * NamedNodeMap's contents. In other words, DO NOT assume either that * index(i) will always refer to the same entry, or that there is any * stable ordering of entries... and be prepared for double-reporting * or skips as insertion and deletion occur. * * @return the node which currenly has the specified index, or null if index * is greater than or equal to getLength(). */ public Node item(int index) { return (nodes != null && index < nodes.size()) ? (Node)(nodes.elementAt(index)) : null; } /** * Retrieve a node by name. * * @param name Name of a node to look up. * @return the Node (of unspecified sub-class) stored with that name, or * null if no value has been assigned to that name. */ public Node getNamedItem(String name) { int i = findNamePoint(name,0); return (i < 0) ? null : (Node)(nodes.elementAt(i)); } // getNamedItem(String):Node /** * Introduced in DOM Level 2.
* Retrieves a node specified by local name and namespace URI. * * @param namespaceURI The namespace URI of the node to retrieve. * When it is null or an empty string, this * method behaves like getNamedItem. * @param localName The local name of the node to retrieve. * @return Node A Node (of any type) with the specified name, or null if the specified * name did not identify any node in the map. */ public Node getNamedItemNS(String namespaceURI, String localName) { int i = findNamePoint(namespaceURI, localName); return (i < 0) ? null : (Node)(nodes.elementAt(i)); } // getNamedItemNS(String,String):Node /** * Adds a node using its nodeName attribute. * As the nodeName attribute is used to derive the name which the node must be * stored under, multiple nodes of certain types (those that have a "special" string * value) cannot be stored as the names would clash. This is seen as preferable to * allowing nodes to be aliased. * @see org.w3c.dom.NamedNodeMap#setNamedItem * @return If the new Node replaces an existing node the replaced Node is returned, * otherwise null is returned. * @param arg * A node to store in a named node map. The node will later be * accessible using the value of the namespaceURI and localName * attribute of the node. If a node with those namespace URI and * local name is already present in the map, it is replaced by the new * one. * @exception org.w3c.dom.DOMException The exception description. */ public Node setNamedItem(Node arg) throws DOMException { if (isReadOnly()) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); } if (arg.getOwnerDocument() != ownerNode.ownerDocument()) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); } int i = findNamePoint(arg.getNodeName(),0); NodeImpl previous = null; if (i >= 0) { previous = (NodeImpl) nodes.elementAt(i); nodes.setElementAt(arg,i); } else { i = -1 - i; // Insert point (may be end of list) if (null == nodes) { nodes = new Vector(5, 10); } nodes.insertElementAt(arg, i); } return previous; } // setNamedItem(Node):Node /** * Adds a node using its namespaceURI and localName. * @see org.w3c.dom.NamedNodeMap#setNamedItem * @return If the new Node replaces an existing node the replaced Node is returned, * otherwise null is returned. * @param arg A node to store in a named node map. The node will later be * accessible using the value of the namespaceURI and localName * attribute of the node. If a node with those namespace URI and * local name is already present in the map, it is replaced by the new * one. */ public Node setNamedItemNS(Node arg) throws DOMException { if (isReadOnly()) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); } if(arg.getOwnerDocument() != ownerNode.ownerDocument()) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); } int i = findNamePoint(arg.getNamespaceURI(), arg.getLocalName()); NodeImpl previous = null; if (i >= 0) { previous = (NodeImpl) nodes.elementAt(i); nodes.setElementAt(arg,i); } else { // If we can't find by namespaceURI, localName, then we find by // nodeName so we know where to insert. i = findNamePoint(arg.getNodeName(),0); if (i >=0) { previous = (NodeImpl) nodes.elementAt(i); nodes.insertElementAt(arg,i); } else { i = -1 - i; // Insert point (may be end of list) if (null == nodes) { nodes = new Vector(5, 10); } nodes.insertElementAt(arg, i); } } return previous; } // setNamedItem(Node):Node /** * Removes a node specified by name. * @param name The name of a node to remove. * @return The node removed from the map if a node with such a name exists. */ /***/ public Node removeNamedItem(String name) throws DOMException { if (isReadOnly()) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); } int i = findNamePoint(name,0); if (i < 0) { String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null); throw new DOMException(DOMException.NOT_FOUND_ERR, msg); } NodeImpl n = (NodeImpl)nodes.elementAt(i); nodes.removeElementAt(i); return n; } // removeNamedItem(String):Node /** * Introduced in DOM Level 2.
* Removes a node specified by local name and namespace URI.
* @param namespaceURI
* The namespace URI of the node to remove.
* When it is null or an empty string, this
* method behaves like removeNamedItem.
* @param The local name of the node to remove.
* @return Node The node removed from the map if a node with such
* a local name and namespace URI exists.
* @throws NOT_FOUND_ERR: Raised if there is no node named
* name in the map.
*/
public Node removeNamedItemNS(String namespaceURI, String name)
throws DOMException {
if (isReadOnly()) {
String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
throw
new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
msg);
}
int i = findNamePoint(namespaceURI, name);
if (i < 0) {
String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
}
NodeImpl n = (NodeImpl)nodes.elementAt(i);
nodes.removeElementAt(i);
return n;
} // removeNamedItem(String):Node
//
// Public methods
//
/**
* Cloning a NamedNodeMap is a DEEP OPERATION; it always clones
* all the nodes contained in the map.
*/
public NamedNodeMapImpl cloneMap(NodeImpl ownerNode) {
NamedNodeMapImpl newmap = new NamedNodeMapImpl(ownerNode);
newmap.cloneContent(this);
return newmap;
}
protected void cloneContent(NamedNodeMapImpl srcmap) {
Vector srcnodes = srcmap.nodes;
if (srcnodes != null) {
int size = srcnodes.size();
if (size != 0) {
if (nodes == null) {
nodes = new Vector(size);
}
nodes.setSize(size);
for (int i = 0; i < size; ++i) {
NodeImpl n = (NodeImpl) srcmap.nodes.elementAt(i);
NodeImpl clone = (NodeImpl) n.cloneNode(true);
clone.isSpecified(n.isSpecified());
nodes.setElementAt(clone, i);
}
}
}
} // cloneMap():NamedNodeMapImpl
//
// Package methods
//
/**
* Internal subroutine to allow read-only Nodes to make their contained
* NamedNodeMaps readonly too. I expect that in fact the shallow
* version of this operation will never be
*
* @param readOnly boolean true to make read-only, false to permit editing.
* @param deep boolean true to pass this request along to the contained
* nodes, false to only toggle the NamedNodeMap itself. I expect that
* the shallow version of this operation will never be used, but I want
* to design it in now, while I'm thinking about it.
*/
void setReadOnly(boolean readOnly, boolean deep) {
isReadOnly(readOnly);
if (deep && nodes != null) {
for (int i = nodes.size() - 1; i >= 0; i--) {
((NodeImpl) nodes.elementAt(i)).setReadOnly(readOnly,deep);
}
}
} // setReadOnly(boolean,boolean)
/**
* Internal subroutine returns this NodeNameMap's (shallow) readOnly value.
*
*/
boolean getReadOnly() {
return isReadOnly();
} // getReadOnly()
//
// Protected methods
//
/**
* NON-DOM
* set the ownerDocument of this node, and the attributes it contains
*/
void setOwnerDocument(CoreDocumentImpl doc) {
if (nodes != null) {
for (int i = 0; i < nodes.size(); i++) {
((NodeImpl)item(i)).setOwnerDocument(doc);
}
}
}
final boolean isReadOnly() {
return (flags & READONLY) != 0;
}
final void isReadOnly(boolean value) {
flags = (short) (value ? flags | READONLY : flags & ~READONLY);
}
final boolean changed() {
return (flags & CHANGED) != 0;
}
final void changed(boolean value) {
flags = (short) (value ? flags | CHANGED : flags & ~CHANGED);
}
final boolean hasDefaults() {
return (flags & HASDEFAULTS) != 0;
}
final void hasDefaults(boolean value) {
flags = (short) (value ? flags | HASDEFAULTS : flags & ~HASDEFAULTS);
}
//
// Private methods
//
/**
* Subroutine: Locate the named item, or the point at which said item
* should be added.
*
* @param name Name of a node to look up.
*
* @return If positive or zero, the index of the found item.
* If negative, index of the appropriate point at which to insert
* the item, encoded as -1-index and hence reconvertable by subtracting
* it from -1. (Encoding because I don't want to recompare the strings
* but don't want to burn bytes on a datatype to hold a flagged value.)
*/
protected int findNamePoint(String name, int start) {
// Binary search
int i = 0;
if (nodes != null) {
int first = start;
int last = nodes.size() - 1;
while (first <= last) {
i = (first + last) / 2;
int test = name.compareTo(((Node)(nodes.elementAt(i))).getNodeName());
if (test == 0) {
return i; // Name found
}
else if (test < 0) {
last = i - 1;
}
else {
first = i + 1;
}
}
if (first > i) {
i = first;
}
}
return -1 - i; // not-found has to be encoded.
} // findNamePoint(String):int
/** This findNamePoint is for DOM Level 2 Namespaces.
*/
protected int findNamePoint(String namespaceURI, String name) {
if (nodes == null) return -1;
if (name == null) return -1;
// This is a linear search through the same nodes Vector.
// The Vector is sorted on the DOM Level 1 nodename.
// The DOM Level 2 NS keys are namespaceURI and Localname,
// so we must linear search thru it.
// In addition, to get this to work with nodes without any namespace
// (namespaceURI and localNames are both null) we then use the nodeName
// as a seconday key.
for (int i = 0; i < nodes.size(); i++) {
NodeImpl a = (NodeImpl)nodes.elementAt(i);
String aNamespaceURI = a.getNamespaceURI();
String aLocalName = a.getLocalName();
if (namespaceURI == null) {
if (aNamespaceURI == null
&&
(name.equals(aLocalName)
||
(aLocalName == null && name.equals(a.getNodeName()))))
return i;
} else {
if (namespaceURI.equals(aNamespaceURI)
&&
name.equals(aLocalName))
return i;
}
}
return -1;
}
// compare 2 nodes in the map. If a precedes b, return true, otherwise
// return false
protected boolean precedes(Node a, Node b) {
if (nodes != null) {
for (int i = 0; i < nodes.size(); i++) {
Node n = (Node)nodes.elementAt(i);
if (n==a) return true;
if (n==b) return false;
}
}
return false;
}
/**
* NON-DOM: Remove attribute at specified index
*/
protected void removeItem(int index) {
if (nodes != null && index < nodes.size()){
nodes.removeElementAt(index);
}
}
protected Object getItem (int index){
if (nodes !=null) {
return nodes.elementAt(index);
}
return null;
}
protected int addItem (Node arg) {
int i = findNamePoint(arg.getNamespaceURI(), arg.getLocalName());
NodeImpl previous = null;
if (i >= 0) {
previous = (NodeImpl) nodes.elementAt(i);
nodes.setElementAt(arg,i);
} else {
// If we can't find by namespaceURI, localName, then we find by
// nodeName so we know where to insert.
i = findNamePoint(arg.getNodeName(),0);
if (i >=0) {
previous = (NodeImpl) nodes.elementAt(i);
nodes.insertElementAt(arg,i);
} else {
i = -1 - i; // Insert point (may be end of list)
if (null == nodes) {
nodes = new Vector(5, 10);
}
nodes.insertElementAt(arg, i);
}
}
return i;
}
/**
* NON-DOM: copy content of this map into the specified vector
*
* @param list Vector to copy information into.
* @return A copy of this node named map
*/
protected Vector cloneMap(Vector list){
if (list == null) {
list = new Vector(5, 10);
}
list.setSize(0);
if (nodes != null) {
for (int i=0; i