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