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.util.ArrayList; 25 import java.util.List; 26 import org.w3c.dom.DOMException; 27 import org.w3c.dom.Node; 28 29 /** 30 * AttributeMap inherits from NamedNodeMapImpl and extends it to deal with the 31 * specifics of storing attributes. These are: 32 * <ul> 33 * <li>managing ownership of attribute nodes 34 * <li>managing default attributes 35 * <li>firing mutation events 36 * </ul> 37 * <p> 38 * This class doesn't directly support mutation events, however, it notifies 39 * the document when mutations are performed so that the document class do so. 40 * 41 * @xerces.internal 42 * 43 */ 44 public class AttributeMap extends NamedNodeMapImpl { 45 46 /** Serialization version. */ 47 static final long serialVersionUID = 8872606282138665383L; 48 49 // 50 // Constructors 51 // 52 53 /** Constructs a named node map. */ 54 protected AttributeMap(ElementImpl ownerNode, NamedNodeMapImpl defaults) { 55 super(ownerNode); 56 if (defaults != null) { 57 // initialize map with the defaults 58 cloneContent(defaults); 59 if (nodes != null) { 60 hasDefaults(true); 61 } 62 } 63 } 64 65 /** 66 * Adds an attribute using its nodeName attribute. 67 * @see org.w3c.dom.NamedNodeMap#setNamedItem 68 * @return If the new Node replaces an existing node the replaced Node is 69 * returned, otherwise null is returned. 70 * @param arg 71 * An Attr node to store in this map. 72 * @exception org.w3c.dom.DOMException The exception description. 73 */ 74 public Node setNamedItem(Node arg) 75 throws DOMException { 76 77 boolean errCheck = ownerNode.ownerDocument().errorChecking; 78 if (errCheck) { 79 if (isReadOnly()) { 80 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); 81 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); 82 } 83 if (arg.getOwnerDocument() != ownerNode.ownerDocument()) { 84 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); 85 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); 86 } 87 if (arg.getNodeType() != Node.ATTRIBUTE_NODE) { 88 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null); 89 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, msg); 90 } 91 } 92 AttrImpl argn = (AttrImpl)arg; 93 94 if (argn.isOwned()){ 95 if (errCheck && argn.getOwnerElement() != ownerNode) { 96 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INUSE_ATTRIBUTE_ERR", null); 97 throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, msg); 98 } 99 // replacing an Attribute with itself does nothing 100 return arg; 101 } 102 103 104 // set owner 105 argn.ownerNode = ownerNode; 106 argn.isOwned(true); 107 108 int i = findNamePoint(argn.getNodeName(),0); 109 AttrImpl previous = null; 110 if (i >= 0) { 111 previous = (AttrImpl) nodes.get(i); 112 nodes.set(i, arg); 113 previous.ownerNode = ownerNode.ownerDocument(); 114 previous.isOwned(false); 115 // make sure it won't be mistaken with defaults in case it's reused 116 previous.isSpecified(true); 117 } else { 118 i = -1 - i; // Insert point (may be end of list) 119 if (null == nodes) { 120 nodes = new ArrayList<>(5); 121 } 122 nodes.add(i, arg); 123 } 124 125 // notify document 126 ownerNode.ownerDocument().setAttrNode(argn, previous); 127 128 // If the new attribute is not normalized, 129 // the owning element is inherently not normalized. 130 if (!argn.isNormalized()) { 131 ownerNode.isNormalized(false); 132 } 133 return previous; 134 135 } // setNamedItem(Node):Node 136 137 /** 138 * Adds an attribute using its namespaceURI and localName. 139 * @see org.w3c.dom.NamedNodeMap#setNamedItem 140 * @return If the new Node replaces an existing node the replaced Node is 141 * returned, otherwise null is returned. 142 * @param arg A node to store in a named node map. 143 */ 144 public Node setNamedItemNS(Node arg) 145 throws DOMException { 146 147 boolean errCheck = ownerNode.ownerDocument().errorChecking; 148 if (errCheck) { 149 if (isReadOnly()) { 150 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); 151 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); 152 } 153 if(arg.getOwnerDocument() != ownerNode.ownerDocument()) { 154 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null); 155 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg); 156 } 157 if (arg.getNodeType() != Node.ATTRIBUTE_NODE) { 158 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null); 159 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, msg); 160 } 161 } 162 AttrImpl argn = (AttrImpl)arg; 163 164 if (argn.isOwned()){ 165 if (errCheck && argn.getOwnerElement() != ownerNode) { 166 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INUSE_ATTRIBUTE_ERR", null); 167 throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, msg); 168 } 169 // replacing an Attribute with itself does nothing 170 return arg; 171 } 172 173 // set owner 174 argn.ownerNode = ownerNode; 175 argn.isOwned(true); 176 177 int i = findNamePoint(argn.getNamespaceURI(), argn.getLocalName()); 178 AttrImpl previous = null; 179 if (i >= 0) { 180 previous = (AttrImpl) nodes.get(i); 181 nodes.set(i, arg); 182 previous.ownerNode = ownerNode.ownerDocument(); 183 previous.isOwned(false); 184 // make sure it won't be mistaken with defaults in case it's reused 185 previous.isSpecified(true); 186 } else { 187 // If we can't find by namespaceURI, localName, then we find by 188 // nodeName so we know where to insert. 189 i = findNamePoint(arg.getNodeName(),0); 190 if (i >=0) { 191 previous = (AttrImpl) nodes.get(i); 192 nodes.add(i, arg); 193 } else { 194 i = -1 - i; // Insert point (may be end of list) 195 if (null == nodes) { 196 nodes = new ArrayList<>(5); 197 } 198 nodes.add(i, arg); 199 } 200 } 201 // changed(true); 202 203 // notify document 204 ownerNode.ownerDocument().setAttrNode(argn, previous); 205 206 // If the new attribute is not normalized, 207 // the owning element is inherently not normalized. 208 if (!argn.isNormalized()) { 209 ownerNode.isNormalized(false); 210 } 211 return previous; 212 213 } // setNamedItemNS(Node):Node 214 215 /** 216 * Removes an attribute specified by name. 217 * @param name 218 * The name of a node to remove. If the 219 * removed attribute is known to have a default value, an 220 * attribute immediately appears containing the default value 221 * as well as the corresponding namespace URI, local name, 222 * and prefix when applicable. 223 * @return The node removed from the map if a node with such a name exists. 224 * @throws NOT_FOUND_ERR: Raised if there is no node named 225 * name in the map. 226 */ 227 /***/ 228 public Node removeNamedItem(String name) 229 throws DOMException { 230 return internalRemoveNamedItem(name, true); 231 } 232 233 /** 234 * Same as removeNamedItem except that it simply returns null if the 235 * specified name is not found. 236 */ 237 Node safeRemoveNamedItem(String name) { 238 return internalRemoveNamedItem(name, false); 239 } 240 241 242 /** 243 * NON-DOM: Remove the node object 244 * 245 * NOTE: Specifically removes THIS NODE -- not the node with this 246 * name, nor the node with these contents. If node does not belong to 247 * this named node map, we throw a DOMException. 248 * 249 * @param item The node to remove 250 * @param addDefault true -- magically add default attribute 251 * @return Removed node 252 * @exception DOMException 253 */ 254 protected Node removeItem(Node item, boolean addDefault) 255 throws DOMException { 256 257 int index = -1; 258 if (nodes != null) { 259 final int size = nodes.size(); 260 for (int i = 0; i < size; ++i) { 261 if (nodes.get(i) == item) { 262 index = i; 263 break; 264 } 265 } 266 } 267 if (index < 0) { 268 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null); 269 throw new DOMException(DOMException.NOT_FOUND_ERR, msg); 270 } 271 272 return remove((AttrImpl)item, index, addDefault); 273 } 274 275 /** 276 * Internal removeNamedItem method allowing to specify whether an exception 277 * must be thrown if the specified name is not found. 278 */ 279 final protected Node internalRemoveNamedItem(String name, boolean raiseEx){ 280 if (isReadOnly()) { 281 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); 282 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); 283 } 284 int i = findNamePoint(name,0); 285 if (i < 0) { 286 if (raiseEx) { 287 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null); 288 throw new DOMException(DOMException.NOT_FOUND_ERR, msg); 289 } else { 290 return null; 291 } 292 } 293 294 return remove((AttrImpl)nodes.get(i), i, true); 295 296 } // internalRemoveNamedItem(String,boolean):Node 297 298 private final Node remove(AttrImpl attr, int index, 299 boolean addDefault) { 300 301 CoreDocumentImpl ownerDocument = ownerNode.ownerDocument(); 302 String name = attr.getNodeName(); 303 if (attr.isIdAttribute()) { 304 ownerDocument.removeIdentifier(attr.getValue()); 305 } 306 307 if (hasDefaults() && addDefault) { 308 // If there's a default, add it instead 309 NamedNodeMapImpl defaults = 310 ((ElementImpl) ownerNode).getDefaultAttributes(); 311 312 Node d; 313 if (defaults != null && 314 (d = defaults.getNamedItem(name)) != null && 315 findNamePoint(name, index+1) < 0) { 316 NodeImpl clone = (NodeImpl)d.cloneNode(true); 317 if (d.getLocalName() !=null){ 318 // we must rely on the name to find a default attribute 319 // ("test:attr"), but while copying it from the DOCTYPE 320 // we should not loose namespace URI that was assigned 321 // to the attribute in the instance document. 322 ((AttrNSImpl)clone).namespaceURI = attr.getNamespaceURI(); 323 } 324 clone.ownerNode = ownerNode; 325 clone.isOwned(true); 326 clone.isSpecified(false); 327 328 nodes.set(index, clone); 329 if (attr.isIdAttribute()) { 330 ownerDocument.putIdentifier(clone.getNodeValue(), 331 (ElementImpl)ownerNode); 332 } 333 } else { 334 nodes.remove(index); 335 } 336 } else { 337 nodes.remove(index); 338 } 339 340 // changed(true); 341 342 // remove reference to owner 343 attr.ownerNode = ownerDocument; 344 attr.isOwned(false); 345 346 // make sure it won't be mistaken with defaults in case it's 347 // reused 348 attr.isSpecified(true); 349 attr.isIdAttribute(false); 350 351 // notify document 352 ownerDocument.removedAttrNode(attr, ownerNode, name); 353 354 return attr; 355 } 356 357 /** 358 * Introduced in DOM Level 2. <p> 359 * Removes an attribute specified by local name and namespace URI. 360 * @param namespaceURI 361 * The namespace URI of the node to remove. 362 * When it is null or an empty string, this 363 * method behaves like removeNamedItem. 364 * @param name The local name of the node to remove. If the 365 * removed attribute is known to have a default 366 * value, an attribute immediately appears 367 * containing the default value. 368 * @return Node The node removed from the map if a node with such 369 * a local name and namespace URI exists. 370 * @throws NOT_FOUND_ERR: Raised if there is no node named 371 * name in the map. 372 */ 373 public Node removeNamedItemNS(String namespaceURI, String name) 374 throws DOMException { 375 return internalRemoveNamedItemNS(namespaceURI, name, true); 376 } 377 378 /** 379 * Same as removeNamedItem except that it simply returns null if the 380 * specified local name and namespace URI is not found. 381 */ 382 Node safeRemoveNamedItemNS(String namespaceURI, String name) { 383 return internalRemoveNamedItemNS(namespaceURI, name, false); 384 } 385 386 /** 387 * Internal removeNamedItemNS method allowing to specify whether an 388 * exception must be thrown if the specified local name and namespace URI 389 * is not found. 390 */ 391 final protected Node internalRemoveNamedItemNS(String namespaceURI, 392 String name, 393 boolean raiseEx) { 394 395 CoreDocumentImpl ownerDocument = ownerNode.ownerDocument(); 396 if (ownerDocument.errorChecking && isReadOnly()) { 397 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null); 398 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg); 399 } 400 int i = findNamePoint(namespaceURI, name); 401 if (i < 0) { 402 if (raiseEx) { 403 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null); 404 throw new DOMException(DOMException.NOT_FOUND_ERR, msg); 405 } else { 406 return null; 407 } 408 } 409 410 AttrImpl n = (AttrImpl)nodes.get(i); 411 412 if (n.isIdAttribute()) { 413 ownerDocument.removeIdentifier(n.getValue()); 414 } 415 // If there's a default, add it instead 416 String nodeName = n.getNodeName(); 417 if (hasDefaults()) { 418 NamedNodeMapImpl defaults = ((ElementImpl) ownerNode).getDefaultAttributes(); 419 Node d; 420 if (defaults != null 421 && (d = defaults.getNamedItem(nodeName)) != null) 422 { 423 int j = findNamePoint(nodeName,0); 424 if (j>=0 && findNamePoint(nodeName, j+1) < 0) { 425 NodeImpl clone = (NodeImpl)d.cloneNode(true); 426 clone.ownerNode = ownerNode; 427 if (d.getLocalName() != null) { 428 // we must rely on the name to find a default attribute 429 // ("test:attr"), but while copying it from the DOCTYPE 430 // we should not loose namespace URI that was assigned 431 // to the attribute in the instance document. 432 ((AttrNSImpl)clone).namespaceURI = namespaceURI; 433 } 434 clone.isOwned(true); 435 clone.isSpecified(false); 436 nodes.set(i, clone); 437 if (clone.isIdAttribute()) { 438 ownerDocument.putIdentifier(clone.getNodeValue(), 439 (ElementImpl)ownerNode); 440 } 441 } else { 442 nodes.remove(i); 443 } 444 } else { 445 nodes.remove(i); 446 } 447 } else { 448 nodes.remove(i); 449 } 450 451 // changed(true); 452 453 // remove reference to owner 454 n.ownerNode = ownerDocument; 455 n.isOwned(false); 456 // make sure it won't be mistaken with defaults in case it's 457 // reused 458 n.isSpecified(true); 459 // update id table if needed 460 n.isIdAttribute(false); 461 462 // notify document 463 ownerDocument.removedAttrNode(n, ownerNode, name); 464 465 return n; 466 467 } // internalRemoveNamedItemNS(String,String,boolean):Node 468 469 // 470 // Public methods 471 // 472 473 /** 474 * Cloning a NamedNodeMap is a DEEP OPERATION; it always clones 475 * all the nodes contained in the map. 476 */ 477 478 public NamedNodeMapImpl cloneMap(NodeImpl ownerNode) { 479 AttributeMap newmap = 480 new AttributeMap((ElementImpl) ownerNode, null); 481 newmap.hasDefaults(hasDefaults()); 482 newmap.cloneContent(this); 483 return newmap; 484 } // cloneMap():AttributeMap 485 486 /** 487 * Override parent's method to set the ownerNode correctly 488 */ 489 protected void cloneContent(NamedNodeMapImpl srcmap) { 490 List<Node> srcnodes = srcmap.nodes; 491 if (srcnodes != null) { 492 int size = srcnodes.size(); 493 if (size != 0) { 494 if (nodes == null) { 495 nodes = new ArrayList<>(size); 496 } 497 else { 498 nodes.clear(); 499 } 500 for (int i = 0; i < size; ++i) { 501 NodeImpl n = (NodeImpl) srcnodes.get(i); 502 NodeImpl clone = (NodeImpl) n.cloneNode(true); 503 clone.isSpecified(n.isSpecified()); 504 nodes.add(clone); 505 clone.ownerNode = ownerNode; 506 clone.isOwned(true); 507 } 508 } 509 } 510 } // cloneContent():AttributeMap 511 512 513 /** 514 * Move specified attributes from the given map to this one 515 */ 516 void moveSpecifiedAttributes(AttributeMap srcmap) { 517 int nsize = (srcmap.nodes != null) ? srcmap.nodes.size() : 0; 518 for (int i = nsize - 1; i >= 0; i--) { 519 AttrImpl attr = (AttrImpl) srcmap.nodes.get(i); 520 if (attr.isSpecified()) { 521 srcmap.remove(attr, i, false); 522 if (attr.getLocalName() != null) { 523 setNamedItem(attr); 524 } 525 else { 526 setNamedItemNS(attr); 527 } 528 } 529 } 530 } // moveSpecifiedAttributes(AttributeMap):void 531 532 533 /** 534 * Get this AttributeMap in sync with the given "defaults" map. 535 * @param defaults The default attributes map to sync with. 536 */ 537 protected void reconcileDefaults(NamedNodeMapImpl defaults) { 538 539 // remove any existing default 540 int nsize = (nodes != null) ? nodes.size() : 0; 541 for (int i = nsize - 1; i >= 0; --i) { 542 AttrImpl attr = (AttrImpl) nodes.get(i); 543 if (!attr.isSpecified()) { 544 remove(attr, i, false); 545 } 546 } 547 // add the new defaults 548 if (defaults == null) { 549 return; 550 } 551 if (nodes == null || nodes.size() == 0) { 552 cloneContent(defaults); 553 } 554 else { 555 int dsize = defaults.nodes.size(); 556 for (int n = 0; n < dsize; ++n) { 557 AttrImpl d = (AttrImpl) defaults.nodes.get(n); 558 int i = findNamePoint(d.getNodeName(), 0); 559 if (i < 0) { 560 i = -1 - i; 561 NodeImpl clone = (NodeImpl) d.cloneNode(true); 562 clone.ownerNode = ownerNode; 563 clone.isOwned(true); 564 clone.isSpecified(false); 565 nodes.add(i, clone); 566 } 567 } 568 } 569 570 } // reconcileDefaults() 571 572 protected final int addItem (Node arg) { 573 574 final AttrImpl argn = (AttrImpl) arg; 575 576 // set owner 577 argn.ownerNode = ownerNode; 578 argn.isOwned(true); 579 580 int i = findNamePoint(argn.getNamespaceURI(), argn.getLocalName()); 581 if (i >= 0) { 582 nodes.set(i, arg); 583 } 584 else { 585 // If we can't find by namespaceURI, localName, then we find by 586 // nodeName so we know where to insert. 587 i = findNamePoint(argn.getNodeName(),0); 588 if (i >= 0) { 589 nodes.add(i, arg); 590 } 591 else { 592 i = -1 - i; // Insert point (may be end of list) 593 if (null == nodes) { 594 nodes = new ArrayList<>(5); 595 } 596 nodes.add(i, arg); 597 } 598 } 599 600 // notify document 601 ownerNode.ownerDocument().setAttrNode(argn, null); 602 return i; 603 } 604 605 } // class AttributeMap