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