/* * 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 * . */ package com.sun.org.apache.xerces.internal.dom; import java.io.Serializable; import java.util.Vector; import org.w3c.dom.DOMException; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; /** * NamedNodeMaps represent collections of Nodes that can be accessed * by name. Entity and Notation nodes are stored in NamedNodeMaps * attached to the DocumentType. Attributes are placed in a NamedNodeMap * attached to the elem they're related too. However, because attributes * require more work, such as firing mutation events, they are stored in * a subclass of NamedNodeMapImpl. *

* 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