1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 * @LastModified: Oct 2017 4 */ 5 /* 6 * Licensed to the Apache Software Foundation (ASF) under one or more 7 * contributor license agreements. See the NOTICE file distributed with 8 * this work for additional information regarding copyright ownership. 9 * The ASF licenses this file to You under the Apache License, Version 2.0 10 * (the "License"); you may not use this file except in compliance with 11 * the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, software 16 * distributed under the License is distributed on an "AS IS" BASIS, 17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 * See the License for the specific language governing permissions and 19 * limitations under the License. 20 */ 21 22 package com.sun.org.apache.xerces.internal.dom; 23 24 import java.io.IOException; 25 import java.io.ObjectInputStream; 26 import java.io.ObjectOutputStream; 27 import java.io.Serializable; 28 import java.util.ArrayList; 29 import java.util.List; 30 import java.util.Vector; 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 * @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<Node> 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()) ? (nodes.get(index)) : null; 125 } 126 127 /** 128 * Retrieve a node by name. 129 * 130 * @param name Name of a node to look up. 131 * @return the Node (of unspecified sub-class) stored with that name, or 132 * null if no value has been assigned to that name. 133 */ 134 public Node getNamedItem(String name) { 135 136 int i = findNamePoint(name,0); 137 return (i < 0) ? null : (nodes.get(i)); 138 139 } // getNamedItem(String):Node 140 141 /** 142 * Introduced in DOM Level 2. <p> 143 * Retrieves a node specified by local name and namespace URI. 144 * 145 * @param namespaceURI The namespace URI of the node to retrieve. 146 * When it is null or an empty string, this 147 * method behaves like getNamedItem. 148 * @param localName The local name of the node to retrieve. 149 * @return Node A Node (of any type) with the specified name, or null if the specified 150 * name did not identify any node in the map. 151 */ 152 public Node getNamedItemNS(String namespaceURI, String localName) { 153 154 int i = findNamePoint(namespaceURI, localName); 155 return (i < 0) ? null : (nodes.get(i)); 156 157 } // getNamedItemNS(String,String):Node 158 159 /** 160 * Adds a node using its nodeName attribute. 161 * As the nodeName attribute is used to derive the name which the node must be 162 * stored under, multiple nodes of certain types (those that have a "special" string 163 * value) cannot be stored as the names would clash. This is seen as preferable to 164 * allowing nodes to be aliased. 165 * @see org.w3c.dom.NamedNodeMap#setNamedItem 166 * @return If the new Node replaces an existing node the replaced Node is returned, 167 * otherwise null is returned. 168 * @param arg 169 * A node to store in a named node map. The node will later be 170 * accessible using the value of the namespaceURI and localName 171 * attribute of the node. If a node with those namespace URI and 172 * local name is already present in the map, it is replaced by the new 173 * one. 174 * @exception org.w3c.dom.DOMException The exception description. 175 */ 176 public Node setNamedItem(Node arg) 177 throws DOMException { 178 179 CoreDocumentImpl ownerDocument = ownerNode.ownerDocument(); 180 if (ownerDocument.errorChecking) { 181 if (isReadOnly()) { 182 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); 183 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); 184 } 185 if (arg.getOwnerDocument() != ownerDocument) { 186 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); 187 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); 188 } 189 } 190 191 int i = findNamePoint(arg.getNodeName(),0); 192 NodeImpl previous = null; 193 if (i >= 0) { 194 previous = (NodeImpl) nodes.get(i); 195 nodes.set(i, arg); 196 } else { 197 i = -1 - i; // Insert point (may be end of list) 198 if (null == nodes) { 199 nodes = new ArrayList<>(5); 200 } 201 nodes.add(i, arg); 202 } 203 return previous; 204 205 } // setNamedItem(Node):Node 206 207 /** 208 * Adds a node using its namespaceURI and localName. 209 * @see org.w3c.dom.NamedNodeMap#setNamedItem 210 * @return If the new Node replaces an existing node the replaced Node is returned, 211 * otherwise null is returned. 212 * @param arg A node to store in a named node map. The node will later be 213 * accessible using the value of the namespaceURI and localName 214 * attribute of the node. If a node with those namespace URI and 215 * local name is already present in the map, it is replaced by the new 216 * one. 217 */ 218 public Node setNamedItemNS(Node arg) 219 throws DOMException { 220 221 CoreDocumentImpl ownerDocument = ownerNode.ownerDocument(); 222 if (ownerDocument.errorChecking) { 223 if (isReadOnly()) { 224 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); 225 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); 226 } 227 228 if(arg.getOwnerDocument() != ownerDocument) { 229 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); 230 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); 231 } 232 } 233 234 int i = findNamePoint(arg.getNamespaceURI(), arg.getLocalName()); 235 NodeImpl previous = null; 236 if (i >= 0) { 237 previous = (NodeImpl) nodes.get(i); 238 nodes.set(i, arg); 239 } else { 240 // If we can't find by namespaceURI, localName, then we find by 241 // nodeName so we know where to insert. 242 i = findNamePoint(arg.getNodeName(),0); 243 if (i >= 0) { 244 previous = (NodeImpl) nodes.get(i); 245 nodes.add(i, arg); 246 } else { 247 i = -1 - i; // Insert point (may be end of list) 248 if (null == nodes) { 249 nodes = new ArrayList<>(5); 250 } 251 nodes.add(i, arg); 252 } 253 } 254 return previous; 255 256 } // setNamedItemNS(Node):Node 257 258 /** 259 * Removes a node specified by name. 260 * @param name The name of a node to remove. 261 * @return The node removed from the map if a node with such a name exists. 262 */ 263 /***/ 264 public Node removeNamedItem(String name) 265 throws DOMException { 266 267 if (isReadOnly()) { 268 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); 269 throw 270 new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, 271 msg); 272 } 273 int i = findNamePoint(name,0); 274 if (i < 0) { 275 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null); 276 throw new DOMException(DOMException.NOT_FOUND_ERR, msg); 277 } 278 279 NodeImpl n = (NodeImpl)nodes.get(i); 280 nodes.remove(i); 281 282 return n; 283 284 } // removeNamedItem(String):Node 285 286 /** 287 * Introduced in DOM Level 2. <p> 288 * Removes a node specified by local name and namespace URI. 289 * @param namespaceURI 290 * The namespace URI of the node to remove. 291 * When it is null or an empty string, this 292 * method behaves like removeNamedItem. 293 * @param name The local name of the node to remove. 294 * @return Node The node removed from the map if a node with such 295 * a local name and namespace URI exists. 296 * @throws NOT_FOUND_ERR: Raised if there is no node named 297 * name in the map. 298 299 */ 300 public Node removeNamedItemNS(String namespaceURI, String name) 301 throws DOMException { 302 303 if (isReadOnly()) { 304 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); 305 throw 306 new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, 307 msg); 308 } 309 int i = findNamePoint(namespaceURI, name); 310 if (i < 0) { 311 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null); 312 throw new DOMException(DOMException.NOT_FOUND_ERR, msg); 313 } 314 315 NodeImpl n = (NodeImpl)nodes.get(i); 316 nodes.remove(i); 317 318 return n; 319 320 } // removeNamedItem(String):Node 321 322 // 323 // Public methods 324 // 325 326 /** 327 * Cloning a NamedNodeMap is a DEEP OPERATION; it always clones 328 * all the nodes contained in the map. 329 */ 330 331 public NamedNodeMapImpl cloneMap(NodeImpl ownerNode) { 332 NamedNodeMapImpl newmap = new NamedNodeMapImpl(ownerNode); 333 newmap.cloneContent(this); 334 return newmap; 335 } 336 337 protected void cloneContent(NamedNodeMapImpl srcmap) { 338 List<Node> srcnodes = srcmap.nodes; 339 if (srcnodes != null) { 340 int size = srcnodes.size(); 341 if (size != 0) { 342 if (nodes == null) { 343 nodes = new ArrayList<>(size); 344 } 345 else { 346 nodes.clear(); 347 } 348 for (int i = 0; i < size; ++i) { 349 NodeImpl n = (NodeImpl) srcmap.nodes.get(i); 350 NodeImpl clone = (NodeImpl) n.cloneNode(true); 351 clone.isSpecified(n.isSpecified()); 352 nodes.add(clone); 353 } 354 } 355 } 356 } // cloneMap():NamedNodeMapImpl 357 358 // 359 // Package methods 360 // 361 362 /** 363 * Internal subroutine to allow read-only Nodes to make their contained 364 * NamedNodeMaps readonly too. I expect that in fact the shallow 365 * version of this operation will never be 366 * 367 * @param readOnly boolean true to make read-only, false to permit editing. 368 * @param deep boolean true to pass this request along to the contained 369 * nodes, false to only toggle the NamedNodeMap itself. I expect that 370 * the shallow version of this operation will never be used, but I want 371 * to design it in now, while I'm thinking about it. 372 */ 373 void setReadOnly(boolean readOnly, boolean deep) { 374 isReadOnly(readOnly); 375 if (deep && nodes != null) { 376 for (int i = nodes.size() - 1; i >= 0; i--) { 377 ((NodeImpl) nodes.get(i)).setReadOnly(readOnly,deep); 378 } 379 } 380 } // setReadOnly(boolean,boolean) 381 382 /** 383 * Internal subroutine returns this NodeNameMap's (shallow) readOnly value. 384 * 385 */ 386 boolean getReadOnly() { 387 return isReadOnly(); 388 } // getReadOnly() 389 390 391 // 392 // Protected methods 393 // 394 395 /** 396 * NON-DOM 397 * set the ownerDocument of this node, and the attributes it contains 398 */ 399 protected void setOwnerDocument(CoreDocumentImpl doc) { 400 if (nodes != null) { 401 final int size = nodes.size(); 402 for (int i = 0; i < size; ++i) { 403 ((NodeImpl)item(i)).setOwnerDocument(doc); 404 } 405 } 406 } 407 408 final boolean isReadOnly() { 409 return (flags & READONLY) != 0; 410 } 411 412 final void isReadOnly(boolean value) { 413 flags = (short) (value ? flags | READONLY : flags & ~READONLY); 414 } 415 416 final boolean changed() { 417 return (flags & CHANGED) != 0; 418 } 419 420 final void changed(boolean value) { 421 flags = (short) (value ? flags | CHANGED : flags & ~CHANGED); 422 } 423 424 final boolean hasDefaults() { 425 return (flags & HASDEFAULTS) != 0; 426 } 427 428 final void hasDefaults(boolean value) { 429 flags = (short) (value ? flags | HASDEFAULTS : flags & ~HASDEFAULTS); 430 } 431 432 // 433 // Private methods 434 // 435 436 /** 437 * Subroutine: Locate the named item, or the point at which said item 438 * should be added. 439 * 440 * @param name Name of a node to look up. 441 * 442 * @return If positive or zero, the index of the found item. 443 * If negative, index of the appropriate point at which to insert 444 * the item, encoded as -1-index and hence reconvertable by subtracting 445 * it from -1. (Encoding because I don't want to recompare the strings 446 * but don't want to burn bytes on a datatype to hold a flagged value.) 447 */ 448 protected int findNamePoint(String name, int start) { 449 450 // Binary search 451 int i = 0; 452 if (nodes != null) { 453 int first = start; 454 int last = nodes.size() - 1; 455 456 while (first <= last) { 457 i = (first + last) / 2; 458 int test = name.compareTo(((nodes.get(i))).getNodeName()); 459 if (test == 0) { 460 return i; // Name found 461 } 462 else if (test < 0) { 463 last = i - 1; 464 } 465 else { 466 first = i + 1; 467 } 468 } 469 470 if (first > i) { 471 i = first; 472 } 473 } 474 475 return -1 - i; // not-found has to be encoded. 476 477 } // findNamePoint(String):int 478 479 480 /** This findNamePoint is for DOM Level 2 Namespaces. 481 */ 482 protected int findNamePoint(String namespaceURI, String name) { 483 484 if (nodes == null) return -1; 485 if (name == null) return -1; 486 487 // This is a linear search through the same nodes ArrayList. 488 // The ArrayList is sorted on the DOM Level 1 nodename. 489 // The DOM Level 2 NS keys are namespaceURI and Localname, 490 // so we must linear search thru it. 491 // In addition, to get this to work with nodes without any namespace 492 // (namespaceURI and localNames are both null) we then use the nodeName 493 // as a secondary key. 494 final int size = nodes.size(); 495 for (int i = 0; i < size; ++i) { 496 NodeImpl a = (NodeImpl)nodes.get(i); 497 String aNamespaceURI = a.getNamespaceURI(); 498 String aLocalName = a.getLocalName(); 499 if (namespaceURI == null) { 500 if (aNamespaceURI == null 501 && 502 (name.equals(aLocalName) 503 || 504 (aLocalName == null && name.equals(a.getNodeName())))) 505 return i; 506 } else { 507 if (namespaceURI.equals(aNamespaceURI) 508 && 509 name.equals(aLocalName)) 510 return i; 511 } 512 } 513 return -1; 514 } 515 516 // compare 2 nodes in the map. If a precedes b, return true, otherwise 517 // return false 518 protected boolean precedes(Node a, Node b) { 519 520 if (nodes != null) { 521 final int size = nodes.size(); 522 for (int i = 0; i < size; ++i) { 523 Node n = nodes.get(i); 524 if (n==a) return true; 525 if (n==b) return false; 526 } 527 } 528 return false; 529 } 530 531 532 /** 533 * NON-DOM: Remove attribute at specified index 534 */ 535 protected void removeItem(int index) { 536 if (nodes != null && index < nodes.size()){ 537 nodes.remove(index); 538 } 539 } 540 541 542 protected Object getItem (int index){ 543 if (nodes != null) { 544 return nodes.get(index); 545 } 546 return null; 547 } 548 549 protected int addItem (Node arg) { 550 int i = findNamePoint(arg.getNamespaceURI(), arg.getLocalName()); 551 if (i >= 0) { 552 nodes.set(i, arg); 553 } 554 else { 555 // If we can't find by namespaceURI, localName, then we find by 556 // nodeName so we know where to insert. 557 i = findNamePoint(arg.getNodeName(),0); 558 if (i >= 0) { 559 nodes.add(i, arg); 560 } 561 else { 562 i = -1 - i; // Insert point (may be end of list) 563 if (null == nodes) { 564 nodes = new ArrayList<>(5); 565 } 566 nodes.add(i, arg); 567 } 568 } 569 return i; 570 } 571 572 /** 573 * NON-DOM: copy content of this map into the specified ArrayList 574 * 575 * @param list ArrayList to copy information into. 576 * @return A copy of this node named map 577 */ 578 protected List<Node> cloneMap(List<Node> list) { 579 if (nodes != null) { 580 list = new ArrayList<>(nodes); 581 } 582 return list; 583 } 584 585 protected int getNamedItemIndex(String namespaceURI, String localName) { 586 return findNamePoint(namespaceURI, localName); 587 } 588 589 /** 590 * NON-DOM remove all elements from this map 591 */ 592 public void removeAll (){ 593 if (nodes != null) { 594 nodes.clear(); 595 } 596 } 597 598 private void readObject(ObjectInputStream in) 599 throws IOException, ClassNotFoundException { 600 in.defaultReadObject(); 601 if (nodes != null) { 602 // nodes are written as a Vector for compatibility. 603 nodes = new ArrayList<>(nodes); 604 } 605 } 606 607 private void writeObject(ObjectOutputStream out) throws IOException { 608 List<Node> oldNodes = this.nodes; 609 try { 610 if (oldNodes != null) { 611 // convert to Vector for backward-compatibility 612 this.nodes = new Vector<>(oldNodes); 613 } 614 out.defaultWriteObject(); 615 } 616 // If the write fails for some reason ensure 617 // that we restore the original object. 618 finally { 619 this.nodes = oldNodes; 620 } 621 } 622 623 } // class NamedNodeMapImpl