View Javadoc
1   /*
2    * reserved comment block
3    * DO NOT REMOVE OR ALTER!
4    */
5   /*
6    * Copyright 1999-2002,2004,2005 The Apache Software Foundation.
7    *
8    * Licensed under the Apache License, Version 2.0 (the "License");
9    * you may not use this file except in compliance with the License.
10   * 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             nodes = new ArrayList(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