1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 */ 4 /* 5 * Copyright 1999-2002,2004,2005 The Apache Software Foundation. 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 */ 19 20 package com.sun.org.apache.xerces.internal.dom; 21 22 import java.io.IOException; 23 import java.io.ObjectInputStream; 24 import java.io.ObjectOutputStream; 25 import java.io.Serializable; 26 import java.util.ArrayList; 27 import java.util.List; 28 import java.util.Vector; 29 30 import org.w3c.dom.DOMException; 31 import org.w3c.dom.NamedNodeMap; 32 import org.w3c.dom.Node; 33 34 /** 35 * NamedNodeMaps represent collections of Nodes that can be accessed 36 * by name. Entity and Notation nodes are stored in NamedNodeMaps 37 * attached to the DocumentType. Attributes are placed in a NamedNodeMap 38 * attached to the elem they're related too. However, because attributes 39 * require more work, such as firing mutation events, they are stored in 40 * a subclass of NamedNodeMapImpl. 41 * <P> 42 * Only one Node may be stored per name; attempting to 43 * store another will replace the previous value. 44 * <P> 45 * NOTE: The "primary" storage key is taken from the NodeName attribute of the 46 * node. The "secondary" storage key is the namespaceURI and localName, when 47 * accessed by DOM level 2 nodes. All nodes, even DOM Level 2 nodes are stored 48 * in a single Vector sorted by the primary "nodename" key. 49 * <P> 50 * NOTE: item()'s integer index does _not_ imply that the named nodes 51 * must be stored in an array; that's only an access method. Note too 52 * that these indices are "live"; if someone changes the map's 53 * contents, the indices associated with nodes may change. 54 * <P> 55 * 56 * @xerces.internal 57 * 58 * @version $Id: NamedNodeMapImpl.java,v 1.8 2010-11-01 04:39:39 joehw Exp $ 59 * @since PR-DOM-Level-1-19980818. 60 */ 61 public class NamedNodeMapImpl 62 implements NamedNodeMap, Serializable { 63 64 // 65 // Constants 66 // 67 68 /** Serialization version. */ 69 static final long serialVersionUID = -7039242451046758020L; 70 71 // 72 // Data 73 // 74 75 protected short flags; 76 77 protected final static short READONLY = 0x1<<0; 78 protected final static short CHANGED = 0x1<<1; 79 protected final static short HASDEFAULTS = 0x1<<2; 80 81 /** Nodes. */ 82 protected List nodes; 83 84 protected NodeImpl ownerNode; // the node this map belongs to 85 86 // 87 // Constructors 88 // 89 90 /** Constructs a named node map. */ 91 protected NamedNodeMapImpl(NodeImpl ownerNode) { 92 this.ownerNode = ownerNode; 93 } 94 95 // 96 // NamedNodeMap methods 97 // 98 99 /** 100 * Report how many nodes are currently stored in this NamedNodeMap. 101 * Caveat: This is a count rather than an index, so the 102 * highest-numbered node at any time can be accessed via 103 * item(getLength()-1). 104 */ 105 public int getLength() { 106 return (nodes != null) ? nodes.size() : 0; 107 } 108 109 /** 110 * Retrieve an item from the map by 0-based index. 111 * 112 * @param index Which item to retrieve. Note that indices are just an 113 * enumeration of the current contents; they aren't guaranteed to be 114 * stable, nor do they imply any promises about the order of the 115 * NamedNodeMap's contents. In other words, DO NOT assume either that 116 * index(i) will always refer to the same entry, or that there is any 117 * stable ordering of entries... and be prepared for double-reporting 118 * or skips as insertion and deletion occur. 119 * 120 * @return the node which currenly has the specified index, or null if index 121 * is greater than or equal to getLength(). 122 */ 123 public Node item(int index) { 124 return (nodes != null && index < nodes.size()) ? 125 (Node)(nodes.get(index)) : null; 126 } 127 128 /** 129 * Retrieve a node by name. 130 * 131 * @param name Name of a node to look up. 132 * @return the Node (of unspecified sub-class) stored with that name, or 133 * null if no value has been assigned to that name. 134 */ 135 public Node getNamedItem(String name) { 136 137 int i = findNamePoint(name,0); 138 return (i < 0) ? null : (Node)(nodes.get(i)); 139 140 } // getNamedItem(String):Node 141 142 /** 143 * Introduced in DOM Level 2. <p> 144 * Retrieves a node specified by local name and namespace URI. 145 * 146 * @param namespaceURI The namespace URI of the node to retrieve. 147 * When it is null or an empty string, this 148 * method behaves like getNamedItem. 149 * @param localName The local name of the node to retrieve. 150 * @return Node A Node (of any type) with the specified name, or null if the specified 151 * name did not identify any node in the map. 152 */ 153 public Node getNamedItemNS(String namespaceURI, String localName) { 154 155 int i = findNamePoint(namespaceURI, localName); 156 return (i < 0) ? null : (Node)(nodes.get(i)); 157 158 } // getNamedItemNS(String,String):Node 159 160 /** 161 * Adds a node using its nodeName attribute. 162 * As the nodeName attribute is used to derive the name which the node must be 163 * stored under, multiple nodes of certain types (those that have a "special" string 164 * value) cannot be stored as the names would clash. This is seen as preferable to 165 * allowing nodes to be aliased. 166 * @see org.w3c.dom.NamedNodeMap#setNamedItem 167 * @return If the new Node replaces an existing node the replaced Node is returned, 168 * otherwise null is returned. 169 * @param arg 170 * A node to store in a named node map. The node will later be 171 * accessible using the value of the namespaceURI and localName 172 * attribute of the node. If a node with those namespace URI and 173 * local name is already present in the map, it is replaced by the new 174 * one. 175 * @exception org.w3c.dom.DOMException The exception description. 176 */ 177 public Node setNamedItem(Node arg) 178 throws DOMException { 179 180 CoreDocumentImpl ownerDocument = ownerNode.ownerDocument(); 181 if (ownerDocument.errorChecking) { 182 if (isReadOnly()) { 183 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); 184 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); 185 } 186 if (arg.getOwnerDocument() != ownerDocument) { 187 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); 188 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); 189 } 190 } 191 192 int i = findNamePoint(arg.getNodeName(),0); 193 NodeImpl previous = null; 194 if (i >= 0) { 195 previous = (NodeImpl) nodes.get(i); 196 nodes.set(i, arg); 197 } else { 198 i = -1 - i; // Insert point (may be end of list) 199 if (null == nodes) { 200 nodes = new ArrayList(5); 201 } 202 nodes.add(i, arg); 203 } 204 return previous; 205 206 } // setNamedItem(Node):Node 207 208 /** 209 * Adds a node using its namespaceURI and localName. 210 * @see org.w3c.dom.NamedNodeMap#setNamedItem 211 * @return If the new Node replaces an existing node the replaced Node is returned, 212 * otherwise null is returned. 213 * @param arg A node to store in a named node map. The node will later be 214 * accessible using the value of the namespaceURI and localName 215 * attribute of the node. If a node with those namespace URI and 216 * local name is already present in the map, it is replaced by the new 217 * one. 218 */ 219 public Node setNamedItemNS(Node arg) 220 throws DOMException { 221 222 CoreDocumentImpl ownerDocument = ownerNode.ownerDocument(); 223 if (ownerDocument.errorChecking) { 224 if (isReadOnly()) { 225 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); 226 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); 227 } 228 229 if(arg.getOwnerDocument() != ownerDocument) { 230 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); 231 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); 232 } 233 } 234 235 int i = findNamePoint(arg.getNamespaceURI(), arg.getLocalName()); 236 NodeImpl previous = null; 237 if (i >= 0) { 238 previous = (NodeImpl) nodes.get(i); 239 nodes.set(i, arg); 240 } else { 241 // If we can't find by namespaceURI, localName, then we find by 242 // nodeName so we know where to insert. 243 i = findNamePoint(arg.getNodeName(),0); 244 if (i >= 0) { 245 previous = (NodeImpl) nodes.get(i); 246 nodes.add(i, arg); 247 } else { 248 i = -1 - i; // Insert point (may be end of list) 249 if (null == nodes) { 250 nodes = new ArrayList(5); 251 } 252 nodes.add(i, arg); 253 } 254 } 255 return previous; 256 257 } // setNamedItemNS(Node):Node 258 259 /** 260 * Removes a node specified by name. 261 * @param name The name of a node to remove. 262 * @return The node removed from the map if a node with such a name exists. 263 */ 264 /***/ 265 public Node removeNamedItem(String name) 266 throws DOMException { 267 268 if (isReadOnly()) { 269 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); 270 throw 271 new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, 272 msg); 273 } 274 int i = findNamePoint(name,0); 275 if (i < 0) { 276 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null); 277 throw new DOMException(DOMException.NOT_FOUND_ERR, msg); 278 } 279 280 NodeImpl n = (NodeImpl)nodes.get(i); 281 nodes.remove(i); 282 283 return n; 284 285 } // removeNamedItem(String):Node 286 287 /** 288 * Introduced in DOM Level 2. <p> 289 * Removes a node specified by local name and namespace URI. 290 * @param namespaceURI 291 * The namespace URI of the node to remove. 292 * When it is null or an empty string, this 293 * method behaves like removeNamedItem. 294 * @param name The local name of the node to remove. 295 * @return Node The node removed from the map if a node with such 296 * a local name and namespace URI exists. 297 * @throws NOT_FOUND_ERR: Raised if there is no node named 298 * name in the map. 299 300 */ 301 public Node removeNamedItemNS(String namespaceURI, String name) 302 throws DOMException { 303 304 if (isReadOnly()) { 305 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); 306 throw 307 new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, 308 msg); 309 } 310 int i = findNamePoint(namespaceURI, name); 311 if (i < 0) { 312 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null); 313 throw new DOMException(DOMException.NOT_FOUND_ERR, msg); 314 } 315 316 NodeImpl n = (NodeImpl)nodes.get(i); 317 nodes.remove(i); 318 319 return n; 320 321 } // removeNamedItem(String):Node 322 323 // 324 // Public methods 325 // 326 327 /** 328 * Cloning a NamedNodeMap is a DEEP OPERATION; it always clones 329 * all the nodes contained in the map. 330 */ 331 332 public NamedNodeMapImpl cloneMap(NodeImpl ownerNode) { 333 NamedNodeMapImpl newmap = new NamedNodeMapImpl(ownerNode); 334 newmap.cloneContent(this); 335 return newmap; 336 } 337 338 protected void cloneContent(NamedNodeMapImpl srcmap) { 339 List srcnodes = srcmap.nodes; 340 if (srcnodes != null) { 341 int size = srcnodes.size(); 342 if (size != 0) { 343 if (nodes == null) { 344 nodes = new ArrayList(size); 345 } 346 else { 347 nodes.clear(); 348 } 349 for (int i = 0; i < size; ++i) { 350 NodeImpl n = (NodeImpl) srcmap.nodes.get(i); 351 NodeImpl clone = (NodeImpl) n.cloneNode(true); 352 clone.isSpecified(n.isSpecified()); 353 nodes.add(clone); 354 } 355 } 356 } 357 } // cloneMap():NamedNodeMapImpl 358 359 // 360 // Package methods 361 // 362 363 /** 364 * Internal subroutine to allow read-only Nodes to make their contained 365 * NamedNodeMaps readonly too. I expect that in fact the shallow 366 * version of this operation will never be 367 * 368 * @param readOnly boolean true to make read-only, false to permit editing. 369 * @param deep boolean true to pass this request along to the contained 370 * nodes, false to only toggle the NamedNodeMap itself. I expect that 371 * the shallow version of this operation will never be used, but I want 372 * to design it in now, while I'm thinking about it. 373 */ 374 void setReadOnly(boolean readOnly, boolean deep) { 375 isReadOnly(readOnly); 376 if (deep && nodes != null) { 377 for (int i = nodes.size() - 1; i >= 0; i--) { 378 ((NodeImpl) nodes.get(i)).setReadOnly(readOnly,deep); 379 } 380 } 381 } // setReadOnly(boolean,boolean) 382 383 /** 384 * Internal subroutine returns this NodeNameMap's (shallow) readOnly value. 385 * 386 */ 387 boolean getReadOnly() { 388 return isReadOnly(); 389 } // getReadOnly() 390 391 392 // 393 // Protected methods 394 // 395 396 /** 397 * NON-DOM 398 * set the ownerDocument of this node, and the attributes it contains 399 */ 400 protected void setOwnerDocument(CoreDocumentImpl doc) { 401 if (nodes != null) { 402 final int size = nodes.size(); 403 for (int i = 0; i < size; ++i) { 404 ((NodeImpl)item(i)).setOwnerDocument(doc); 405 } 406 } 407 } 408 409 final boolean isReadOnly() { 410 return (flags & READONLY) != 0; 411 } 412 413 final void isReadOnly(boolean value) { 414 flags = (short) (value ? flags | READONLY : flags & ~READONLY); 415 } 416 417 final boolean changed() { 418 return (flags & CHANGED) != 0; 419 } 420 421 final void changed(boolean value) { 422 flags = (short) (value ? flags | CHANGED : flags & ~CHANGED); 423 } 424 425 final boolean hasDefaults() { 426 return (flags & HASDEFAULTS) != 0; 427 } 428 429 final void hasDefaults(boolean value) { 430 flags = (short) (value ? flags | HASDEFAULTS : flags & ~HASDEFAULTS); 431 } 432 433 // 434 // Private methods 435 // 436 437 /** 438 * Subroutine: Locate the named item, or the point at which said item 439 * should be added. 440 * 441 * @param name Name of a node to look up. 442 * 443 * @return If positive or zero, the index of the found item. 444 * If negative, index of the appropriate point at which to insert 445 * the item, encoded as -1-index and hence reconvertable by subtracting 446 * it from -1. (Encoding because I don't want to recompare the strings 447 * but don't want to burn bytes on a datatype to hold a flagged value.) 448 */ 449 protected int findNamePoint(String name, int start) { 450 451 // Binary search 452 int i = 0; 453 if (nodes != null) { 454 int first = start; 455 int last = nodes.size() - 1; 456 457 while (first <= last) { 458 i = (first + last) / 2; 459 int test = name.compareTo(((Node)(nodes.get(i))).getNodeName()); 460 if (test == 0) { 461 return i; // Name found 462 } 463 else if (test < 0) { 464 last = i - 1; 465 } 466 else { 467 first = i + 1; 468 } 469 } 470 471 if (first > i) { 472 i = first; 473 } 474 } 475 476 return -1 - i; // not-found has to be encoded. 477 478 } // findNamePoint(String):int 479 480 481 /** This findNamePoint is for DOM Level 2 Namespaces. 482 */ 483 protected int findNamePoint(String namespaceURI, String name) { 484 485 if (nodes == null) return -1; 486 if (name == null) return -1; 487 488 // This is a linear search through the same nodes ArrayList. 489 // The ArrayList is sorted on the DOM Level 1 nodename. 490 // The DOM Level 2 NS keys are namespaceURI and Localname, 491 // so we must linear search thru it. 492 // In addition, to get this to work with nodes without any namespace 493 // (namespaceURI and localNames are both null) we then use the nodeName 494 // as a secondary key. 495 final int size = nodes.size(); 496 for (int i = 0; i < size; ++i) { 497 NodeImpl a = (NodeImpl)nodes.get(i); 498 String aNamespaceURI = a.getNamespaceURI(); 499 String aLocalName = a.getLocalName(); 500 if (namespaceURI == null) { 501 if (aNamespaceURI == null 502 && 503 (name.equals(aLocalName) 504 || 505 (aLocalName == null && name.equals(a.getNodeName())))) 506 return i; 507 } else { 508 if (namespaceURI.equals(aNamespaceURI) 509 && 510 name.equals(aLocalName)) 511 return i; 512 } 513 } 514 return -1; 515 } 516 517 // compare 2 nodes in the map. If a precedes b, return true, otherwise 518 // return false 519 protected boolean precedes(Node a, Node b) { 520 521 if (nodes != null) { 522 final int size = nodes.size(); 523 for (int i = 0; i < size; ++i) { 524 Node n = (Node)nodes.get(i); 525 if (n==a) return true; 526 if (n==b) return false; 527 } 528 } 529 return false; 530 } 531 532 533 /** 534 * NON-DOM: Remove attribute at specified index 535 */ 536 protected void removeItem(int index) { 537 if (nodes != null && index < nodes.size()){ 538 nodes.remove(index); 539 } 540 } 541 542 543 protected Object getItem (int index){ 544 if (nodes != null) { 545 return nodes.get(index); 546 } 547 return null; 548 } 549 550 protected int addItem (Node arg) { 551 int i = findNamePoint(arg.getNamespaceURI(), arg.getLocalName()); 552 if (i >= 0) { 553 nodes.set(i, arg); 554 } 555 else { 556 // If we can't find by namespaceURI, localName, then we find by 557 // nodeName so we know where to insert. 558 i = findNamePoint(arg.getNodeName(),0); 559 if (i >= 0) { 560 nodes.add(i, arg); 561 } 562 else { 563 i = -1 - i; // Insert point (may be end of list) 564 if (null == nodes) { 565 nodes = new ArrayList(5); 566 } 567 nodes.add(i, arg); 568 } 569 } 570 return i; 571 } 572 573 /** 574 * NON-DOM: copy content of this map into the specified ArrayList 575 * 576 * @param list ArrayList to copy information into. 577 * @return A copy of this node named map 578 */ 579 protected ArrayList cloneMap(ArrayList list) { 580 if (list == null) { 581 list = new ArrayList(5); 582 } 583 list.clear(); 584 if (nodes != null) { 585 final int size = nodes.size(); 586 for (int i = 0; i < size; ++i) { 587 list.add(nodes.get(i)); 588 } 589 } 590 return list; 591 } 592 593 protected int getNamedItemIndex(String namespaceURI, String localName) { 594 return findNamePoint(namespaceURI, localName); 595 } 596 597 /** 598 * NON-DOM remove all elements from this map 599 */ 600 public void removeAll (){ 601 if (nodes != null) { 602 nodes.clear(); 603 } 604 } 605 606 private void readObject(ObjectInputStream in) 607 throws IOException, ClassNotFoundException { 608 in.defaultReadObject(); 609 if (nodes != null) { 610 // cast to Vector is required 611 nodes = new ArrayList((Vector)nodes); 612 } 613 } 614 615 private void writeObject(ObjectOutputStream out) throws IOException { 616 List oldNodes = this.nodes; 617 try { 618 if (oldNodes != null) { 619 this.nodes = new Vector(oldNodes); 620 } 621 out.defaultWriteObject(); 622 } 623 // If the write fails for some reason ensure 624 // that we restore the original object. 625 finally { 626 this.nodes = oldNodes; 627 } 628 } 629 630 } // class NamedNodeMapImpl