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