1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. *
  5. * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
  6. * reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * 1. Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * 3. The end-user documentation included with the redistribution,
  21. * if any, must include the following acknowledgment:
  22. * "This product includes software developed by the
  23. * Apache Software Foundation (http://www.apache.org/)."
  24. * Alternately, this acknowledgment may appear in the software itself,
  25. * if and wherever such third-party acknowledgments normally appear.
  26. *
  27. * 4. The names "Xerces" and "Apache Software Foundation" must
  28. * not be used to endorse or promote products derived from this
  29. * software without prior written permission. For written
  30. * permission, please contact apache@apache.org.
  31. *
  32. * 5. Products derived from this software may not be called "Apache",
  33. * nor may "Apache" appear in their name, without prior written
  34. * permission of the Apache Software Foundation.
  35. *
  36. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  37. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  38. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  39. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  40. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  41. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  42. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  43. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  44. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  45. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  46. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  47. * SUCH DAMAGE.
  48. * ====================================================================
  49. *
  50. * This software consists of voluntary contributions made by many
  51. * individuals on behalf of the Apache Software Foundation and was
  52. * originally based on software copyright (c) 1999, International
  53. * Business Machines, Inc., http://www.apache.org. For more
  54. * information on the Apache Software Foundation, please see
  55. * <http://www.apache.org/>.
  56. */
  57. package com.sun.org.apache.xerces.internal.dom;
  58. import java.io.Serializable;
  59. import java.util.Vector;
  60. import org.w3c.dom.DOMException;
  61. import org.w3c.dom.NamedNodeMap;
  62. import org.w3c.dom.Node;
  63. /**
  64. * NamedNodeMaps represent collections of Nodes that can be accessed
  65. * by name. Entity and Notation nodes are stored in NamedNodeMaps
  66. * attached to the DocumentType. Attributes are placed in a NamedNodeMap
  67. * attached to the elem they're related too. However, because attributes
  68. * require more work, such as firing mutation events, they are stored in
  69. * a subclass of NamedNodeMapImpl.
  70. * <P>
  71. * Only one Node may be stored per name; attempting to
  72. * store another will replace the previous value.
  73. * <P>
  74. * NOTE: The "primary" storage key is taken from the NodeName attribute of the
  75. * node. The "secondary" storage key is the namespaceURI and localName, when
  76. * accessed by DOM level 2 nodes. All nodes, even DOM Level 2 nodes are stored
  77. * in a single Vector sorted by the primary "nodename" key.
  78. * <P>
  79. * NOTE: item()'s integer index does _not_ imply that the named nodes
  80. * must be stored in an array; that's only an access method. Note too
  81. * that these indices are "live"; if someone changes the map's
  82. * contents, the indices associated with nodes may change.
  83. * <P>
  84. *
  85. * @version $Id: NamedNodeMapImpl.java,v 1.35 2003/01/23 23:23:25 lmartin Exp $
  86. * @since PR-DOM-Level-1-19980818.
  87. */
  88. public class NamedNodeMapImpl
  89. implements NamedNodeMap, Serializable {
  90. //
  91. // Constants
  92. //
  93. /** Serialization version. */
  94. static final long serialVersionUID = -7039242451046758020L;
  95. //
  96. // Data
  97. //
  98. protected short flags;
  99. protected final static short READONLY = 0x1<<0;
  100. protected final static short CHANGED = 0x1<<1;
  101. protected final static short HASDEFAULTS = 0x1<<2;
  102. /** Nodes. */
  103. protected Vector nodes;
  104. protected NodeImpl ownerNode; // the node this map belongs to
  105. //
  106. // Constructors
  107. //
  108. /** Constructs a named node map. */
  109. protected NamedNodeMapImpl(NodeImpl ownerNode) {
  110. this.ownerNode = ownerNode;
  111. }
  112. //
  113. // NamedNodeMap methods
  114. //
  115. /**
  116. * Report how many nodes are currently stored in this NamedNodeMap.
  117. * Caveat: This is a count rather than an index, so the
  118. * highest-numbered node at any time can be accessed via
  119. * item(getLength()-1).
  120. */
  121. public int getLength() {
  122. return (nodes != null) ? nodes.size() : 0;
  123. }
  124. /**
  125. * Retrieve an item from the map by 0-based index.
  126. *
  127. * @param index Which item to retrieve. Note that indices are just an
  128. * enumeration of the current contents; they aren't guaranteed to be
  129. * stable, nor do they imply any promises about the order of the
  130. * NamedNodeMap's contents. In other words, DO NOT assume either that
  131. * index(i) will always refer to the same entry, or that there is any
  132. * stable ordering of entries... and be prepared for double-reporting
  133. * or skips as insertion and deletion occur.
  134. *
  135. * @return the node which currenly has the specified index, or null if index
  136. * is greater than or equal to getLength().
  137. */
  138. public Node item(int index) {
  139. return (nodes != null && index < nodes.size()) ?
  140. (Node)(nodes.elementAt(index)) : null;
  141. }
  142. /**
  143. * Retrieve a node by name.
  144. *
  145. * @param name Name of a node to look up.
  146. * @return the Node (of unspecified sub-class) stored with that name, or
  147. * null if no value has been assigned to that name.
  148. */
  149. public Node getNamedItem(String name) {
  150. int i = findNamePoint(name,0);
  151. return (i < 0) ? null : (Node)(nodes.elementAt(i));
  152. } // getNamedItem(String):Node
  153. /**
  154. * Introduced in DOM Level 2. <p>
  155. * Retrieves a node specified by local name and namespace URI.
  156. *
  157. * @param namespaceURI The namespace URI of the node to retrieve.
  158. * When it is null or an empty string, this
  159. * method behaves like getNamedItem.
  160. * @param localName The local name of the node to retrieve.
  161. * @return Node A Node (of any type) with the specified name, or null if the specified
  162. * name did not identify any node in the map.
  163. */
  164. public Node getNamedItemNS(String namespaceURI, String localName) {
  165. int i = findNamePoint(namespaceURI, localName);
  166. return (i < 0) ? null : (Node)(nodes.elementAt(i));
  167. } // getNamedItemNS(String,String):Node
  168. /**
  169. * Adds a node using its nodeName attribute.
  170. * As the nodeName attribute is used to derive the name which the node must be
  171. * stored under, multiple nodes of certain types (those that have a "special" string
  172. * value) cannot be stored as the names would clash. This is seen as preferable to
  173. * allowing nodes to be aliased.
  174. * @see org.w3c.dom.NamedNodeMap#setNamedItem
  175. * @return If the new Node replaces an existing node the replaced Node is returned,
  176. * otherwise null is returned.
  177. * @param arg
  178. * A node to store in a named node map. The node will later be
  179. * accessible using the value of the namespaceURI and localName
  180. * attribute of the node. If a node with those namespace URI and
  181. * local name is already present in the map, it is replaced by the new
  182. * one.
  183. * @exception org.w3c.dom.DOMException The exception description.
  184. */
  185. public Node setNamedItem(Node arg)
  186. throws DOMException {
  187. if (isReadOnly()) {
  188. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
  189. throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
  190. }
  191. if (arg.getOwnerDocument() != ownerNode.ownerDocument()) {
  192. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
  193. throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
  194. }
  195. int i = findNamePoint(arg.getNodeName(),0);
  196. NodeImpl previous = null;
  197. if (i >= 0) {
  198. previous = (NodeImpl) nodes.elementAt(i);
  199. nodes.setElementAt(arg,i);
  200. } else {
  201. i = -1 - i; // Insert point (may be end of list)
  202. if (null == nodes) {
  203. nodes = new Vector(5, 10);
  204. }
  205. nodes.insertElementAt(arg, i);
  206. }
  207. return previous;
  208. } // setNamedItem(Node):Node
  209. /**
  210. * Adds a node using its namespaceURI and localName.
  211. * @see org.w3c.dom.NamedNodeMap#setNamedItem
  212. * @return If the new Node replaces an existing node the replaced Node is returned,
  213. * otherwise null is returned.
  214. * @param arg A node to store in a named node map. The node will later be
  215. * accessible using the value of the namespaceURI and localName
  216. * attribute of the node. If a node with those namespace URI and
  217. * local name is already present in the map, it is replaced by the new
  218. * one.
  219. */
  220. public Node setNamedItemNS(Node arg)
  221. throws DOMException {
  222. if (isReadOnly()) {
  223. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
  224. throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
  225. }
  226. if(arg.getOwnerDocument() != ownerNode.ownerDocument()) {
  227. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
  228. throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
  229. }
  230. int i = findNamePoint(arg.getNamespaceURI(), arg.getLocalName());
  231. NodeImpl previous = null;
  232. if (i >= 0) {
  233. previous = (NodeImpl) nodes.elementAt(i);
  234. nodes.setElementAt(arg,i);
  235. } else {
  236. // If we can't find by namespaceURI, localName, then we find by
  237. // nodeName so we know where to insert.
  238. i = findNamePoint(arg.getNodeName(),0);
  239. if (i >=0) {
  240. previous = (NodeImpl) nodes.elementAt(i);
  241. nodes.insertElementAt(arg,i);
  242. } else {
  243. i = -1 - i; // Insert point (may be end of list)
  244. if (null == nodes) {
  245. nodes = new Vector(5, 10);
  246. }
  247. nodes.insertElementAt(arg, i);
  248. }
  249. }
  250. return previous;
  251. } // setNamedItem(Node):Node
  252. /**
  253. * Removes a node specified by name.
  254. * @param name The name of a node to remove.
  255. * @return The node removed from the map if a node with such a name exists.
  256. */
  257. /***/
  258. public Node removeNamedItem(String name)
  259. throws DOMException {
  260. if (isReadOnly()) {
  261. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
  262. throw
  263. new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
  264. msg);
  265. }
  266. int i = findNamePoint(name,0);
  267. if (i < 0) {
  268. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
  269. throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
  270. }
  271. NodeImpl n = (NodeImpl)nodes.elementAt(i);
  272. nodes.removeElementAt(i);
  273. return n;
  274. } // removeNamedItem(String):Node
  275. /**
  276. * Introduced in DOM Level 2. <p>
  277. * Removes a node specified by local name and namespace URI.
  278. * @param namespaceURI
  279. * The namespace URI of the node to remove.
  280. * When it is null or an empty string, this
  281. * method behaves like removeNamedItem.
  282. * @param The local name of the node to remove.
  283. * @return Node The node removed from the map if a node with such
  284. * a local name and namespace URI exists.
  285. * @throws NOT_FOUND_ERR: Raised if there is no node named
  286. * name in the map.
  287. */
  288. public Node removeNamedItemNS(String namespaceURI, String name)
  289. throws DOMException {
  290. if (isReadOnly()) {
  291. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
  292. throw
  293. new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
  294. msg);
  295. }
  296. int i = findNamePoint(namespaceURI, name);
  297. if (i < 0) {
  298. String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
  299. throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
  300. }
  301. NodeImpl n = (NodeImpl)nodes.elementAt(i);
  302. nodes.removeElementAt(i);
  303. return n;
  304. } // removeNamedItem(String):Node
  305. //
  306. // Public methods
  307. //
  308. /**
  309. * Cloning a NamedNodeMap is a DEEP OPERATION; it always clones
  310. * all the nodes contained in the map.
  311. */
  312. public NamedNodeMapImpl cloneMap(NodeImpl ownerNode) {
  313. NamedNodeMapImpl newmap = new NamedNodeMapImpl(ownerNode);
  314. newmap.cloneContent(this);
  315. return newmap;
  316. }
  317. protected void cloneContent(NamedNodeMapImpl srcmap) {
  318. Vector srcnodes = srcmap.nodes;
  319. if (srcnodes != null) {
  320. int size = srcnodes.size();
  321. if (size != 0) {
  322. if (nodes == null) {
  323. nodes = new Vector(size);
  324. }
  325. nodes.setSize(size);
  326. for (int i = 0; i < size; ++i) {
  327. NodeImpl n = (NodeImpl) srcmap.nodes.elementAt(i);
  328. NodeImpl clone = (NodeImpl) n.cloneNode(true);
  329. clone.isSpecified(n.isSpecified());
  330. nodes.setElementAt(clone, i);
  331. }
  332. }
  333. }
  334. } // cloneMap():NamedNodeMapImpl
  335. //
  336. // Package methods
  337. //
  338. /**
  339. * Internal subroutine to allow read-only Nodes to make their contained
  340. * NamedNodeMaps readonly too. I expect that in fact the shallow
  341. * version of this operation will never be
  342. *
  343. * @param readOnly boolean true to make read-only, false to permit editing.
  344. * @param deep boolean true to pass this request along to the contained
  345. * nodes, false to only toggle the NamedNodeMap itself. I expect that
  346. * the shallow version of this operation will never be used, but I want
  347. * to design it in now, while I'm thinking about it.
  348. */
  349. void setReadOnly(boolean readOnly, boolean deep) {
  350. isReadOnly(readOnly);
  351. if (deep && nodes != null) {
  352. for (int i = nodes.size() - 1; i >= 0; i--) {
  353. ((NodeImpl) nodes.elementAt(i)).setReadOnly(readOnly,deep);
  354. }
  355. }
  356. } // setReadOnly(boolean,boolean)
  357. /**
  358. * Internal subroutine returns this NodeNameMap's (shallow) readOnly value.
  359. *
  360. */
  361. boolean getReadOnly() {
  362. return isReadOnly();
  363. } // getReadOnly()
  364. //
  365. // Protected methods
  366. //
  367. /**
  368. * NON-DOM
  369. * set the ownerDocument of this node, and the attributes it contains
  370. */
  371. void setOwnerDocument(CoreDocumentImpl doc) {
  372. if (nodes != null) {
  373. for (int i = 0; i < nodes.size(); i++) {
  374. ((NodeImpl)item(i)).setOwnerDocument(doc);
  375. }
  376. }
  377. }
  378. final boolean isReadOnly() {
  379. return (flags & READONLY) != 0;
  380. }
  381. final void isReadOnly(boolean value) {
  382. flags = (short) (value ? flags | READONLY : flags & ~READONLY);
  383. }
  384. final boolean changed() {
  385. return (flags & CHANGED) != 0;
  386. }
  387. final void changed(boolean value) {
  388. flags = (short) (value ? flags | CHANGED : flags & ~CHANGED);
  389. }
  390. final boolean hasDefaults() {
  391. return (flags & HASDEFAULTS) != 0;
  392. }
  393. final void hasDefaults(boolean value) {
  394. flags = (short) (value ? flags | HASDEFAULTS : flags & ~HASDEFAULTS);
  395. }
  396. //
  397. // Private methods
  398. //
  399. /**
  400. * Subroutine: Locate the named item, or the point at which said item
  401. * should be added.
  402. *
  403. * @param name Name of a node to look up.
  404. *
  405. * @return If positive or zero, the index of the found item.
  406. * If negative, index of the appropriate point at which to insert
  407. * the item, encoded as -1-index and hence reconvertable by subtracting
  408. * it from -1. (Encoding because I don't want to recompare the strings
  409. * but don't want to burn bytes on a datatype to hold a flagged value.)
  410. */
  411. protected int findNamePoint(String name, int start) {
  412. // Binary search
  413. int i = 0;
  414. if (nodes != null) {
  415. int first = start;
  416. int last = nodes.size() - 1;
  417. while (first <= last) {
  418. i = (first + last) / 2;
  419. int test = name.compareTo(((Node)(nodes.elementAt(i))).getNodeName());
  420. if (test == 0) {
  421. return i; // Name found
  422. }
  423. else if (test < 0) {
  424. last = i - 1;
  425. }
  426. else {
  427. first = i + 1;
  428. }
  429. }
  430. if (first > i) {
  431. i = first;
  432. }
  433. }
  434. return -1 - i; // not-found has to be encoded.
  435. } // findNamePoint(String):int
  436. /** This findNamePoint is for DOM Level 2 Namespaces.
  437. */
  438. protected int findNamePoint(String namespaceURI, String name) {
  439. if (nodes == null) return -1;
  440. if (name == null) return -1;
  441. // This is a linear search through the same nodes Vector.
  442. // The Vector is sorted on the DOM Level 1 nodename.
  443. // The DOM Level 2 NS keys are namespaceURI and Localname,
  444. // so we must linear search thru it.
  445. // In addition, to get this to work with nodes without any namespace
  446. // (namespaceURI and localNames are both null) we then use the nodeName
  447. // as a seconday key.
  448. for (int i = 0; i < nodes.size(); i++) {
  449. NodeImpl a = (NodeImpl)nodes.elementAt(i);
  450. String aNamespaceURI = a.getNamespaceURI();
  451. String aLocalName = a.getLocalName();
  452. if (namespaceURI == null) {
  453. if (aNamespaceURI == null
  454. &&
  455. (name.equals(aLocalName)
  456. ||
  457. (aLocalName == null && name.equals(a.getNodeName()))))
  458. return i;
  459. } else {
  460. if (namespaceURI.equals(aNamespaceURI)
  461. &&
  462. name.equals(aLocalName))
  463. return i;
  464. }
  465. }
  466. return -1;
  467. }
  468. // compare 2 nodes in the map. If a precedes b, return true, otherwise
  469. // return false
  470. protected boolean precedes(Node a, Node b) {
  471. if (nodes != null) {
  472. for (int i = 0; i < nodes.size(); i++) {
  473. Node n = (Node)nodes.elementAt(i);
  474. if (n==a) return true;
  475. if (n==b) return false;
  476. }
  477. }
  478. return false;
  479. }
  480. /**
  481. * NON-DOM: Remove attribute at specified index
  482. */
  483. protected void removeItem(int index) {
  484. if (nodes != null && index < nodes.size()){
  485. nodes.removeElementAt(index);
  486. }
  487. }
  488. protected Object getItem (int index){
  489. if (nodes !=null) {
  490. return nodes.elementAt(index);
  491. }
  492. return null;
  493. }
  494. protected int addItem (Node arg) {
  495. int i = findNamePoint(arg.getNamespaceURI(), arg.getLocalName());
  496. NodeImpl previous = null;
  497. if (i >= 0) {
  498. previous = (NodeImpl) nodes.elementAt(i);
  499. nodes.setElementAt(arg,i);
  500. } else {
  501. // If we can't find by namespaceURI, localName, then we find by
  502. // nodeName so we know where to insert.
  503. i = findNamePoint(arg.getNodeName(),0);
  504. if (i >=0) {
  505. previous = (NodeImpl) nodes.elementAt(i);
  506. nodes.insertElementAt(arg,i);
  507. } else {
  508. i = -1 - i; // Insert point (may be end of list)
  509. if (null == nodes) {
  510. nodes = new Vector(5, 10);
  511. }
  512. nodes.insertElementAt(arg, i);
  513. }
  514. }
  515. return i;
  516. }
  517. /**
  518. * NON-DOM: copy content of this map into the specified vector
  519. *
  520. * @param list Vector to copy information into.
  521. * @return A copy of this node named map
  522. */
  523. protected Vector cloneMap(Vector list){
  524. if (list == null) {
  525. list = new Vector(5, 10);
  526. }
  527. list.setSize(0);
  528. if (nodes != null) {
  529. for (int i=0; i<nodes.size(); i++) {
  530. list.insertElementAt(nodes.elementAt(i), i);
  531. }
  532. }
  533. return list;
  534. }
  535. protected int getNamedItemIndex(String namespaceURI, String localName) {
  536. return findNamePoint(namespaceURI, localName);
  537. }
  538. /**
  539. * NON-DOM remove all elements from this map
  540. */
  541. public void removeAll (){
  542. if (nodes != null) {
  543. nodes.removeAllElements();
  544. }
  545. }
  546. } // class NamedNodeMapImpl