1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
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
22package com.sun.org.apache.xerces.internal.dom;
23
24import java.io.IOException;
25import java.io.ObjectInputStream;
26import java.io.ObjectOutputStream;
27import java.io.Serializable;
28import java.util.ArrayList;
29import java.util.List;
30import java.util.Vector;
31
32import org.w3c.dom.DOMException;
33import org.w3c.dom.NamedNodeMap;
34import org.w3c.dom.Node;
35
36/**
37 * NamedNodeMaps represent collections of Nodes that can be accessed
38 * by name. Entity and Notation nodes are stored in NamedNodeMaps
39 * attached to the DocumentType. Attributes are placed in a NamedNodeMap
40 * attached to the elem they're related too. However, because attributes
41 * require more work, such as firing mutation events, they are stored in
42 * a subclass of NamedNodeMapImpl.
43 * <P>
44 * Only one Node may be stored per name; attempting to
45 * store another will replace the previous value.
46 * <P>
47 * NOTE: The "primary" storage key is taken from the NodeName attribute of the
48 * node. The "secondary" storage key is the namespaceURI and localName, when
49 * accessed by DOM level 2 nodes. All nodes, even DOM Level 2 nodes are stored
50 * in a single Vector sorted by the primary "nodename" key.
51 * <P>
52 * NOTE: item()'s integer index does _not_ imply that the named nodes
53 * must be stored in an array; that's only an access method. Note too
54 * that these indices are "live"; if someone changes the map's
55 * contents, the indices associated with nodes may change.
56 * <P>
57 *
58 * @xerces.internal
59 *
60 * @since  PR-DOM-Level-1-19980818.
61 */
62public 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
631