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