View Javadoc
1   /*
2    * reserved comment block
3    * DO NOT REMOVE OR ALTER!
4    */
5   /*
6    * Copyright 2000-2002,2004 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.util.ArrayList;
24  import java.util.List;
25  
26  import org.w3c.dom.DOMException;
27  import org.w3c.dom.Node;
28  
29  /**
30   * AttributeMap inherits from NamedNodeMapImpl and extends it to deal with the
31   * specifics of storing attributes. These are:
32   * <ul>
33   *  <li>managing ownership of attribute nodes
34   *  <li>managing default attributes
35   *  <li>firing mutation events
36   * </ul>
37   * <p>
38   * This class doesn't directly support mutation events, however, it notifies
39   * the document when mutations are performed so that the document class do so.
40   *
41   * @xerces.internal
42   *
43   * @version $Id: AttributeMap.java,v 1.7 2010-11-01 04:39:37 joehw Exp $
44   */
45  public class AttributeMap extends NamedNodeMapImpl {
46  
47      /** Serialization version. */
48      static final long serialVersionUID = 8872606282138665383L;
49  
50      //
51      // Constructors
52      //
53  
54      /** Constructs a named node map. */
55      protected AttributeMap(ElementImpl ownerNode, NamedNodeMapImpl defaults) {
56          super(ownerNode);
57          if (defaults != null) {
58              // initialize map with the defaults
59              cloneContent(defaults);
60              if (nodes != null) {
61                  hasDefaults(true);
62              }
63          }
64      }
65  
66      /**
67       * Adds an attribute using its nodeName attribute.
68       * @see org.w3c.dom.NamedNodeMap#setNamedItem
69       * @return If the new Node replaces an existing node the replaced Node is
70       *      returned, otherwise null is returned.
71       * @param arg
72       *      An Attr node to store in this map.
73       * @exception org.w3c.dom.DOMException The exception description.
74       */
75      public Node setNamedItem(Node arg)
76      throws DOMException {
77  
78          boolean errCheck = ownerNode.ownerDocument().errorChecking;
79          if (errCheck) {
80              if (isReadOnly()) {
81                  String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
82                  throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
83              }
84              if (arg.getOwnerDocument() != ownerNode.ownerDocument()) {
85                  String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
86                  throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
87              }
88              if (arg.getNodeType() != Node.ATTRIBUTE_NODE) {
89                  String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null);
90                  throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, msg);
91              }
92          }
93          AttrImpl argn = (AttrImpl)arg;
94  
95          if (argn.isOwned()){
96              if (errCheck && argn.getOwnerElement() != ownerNode) {
97                  String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INUSE_ATTRIBUTE_ERR", null);
98                  throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, msg);
99              }
100             // replacing an Attribute with itself does nothing
101             return arg;
102         }
103 
104 
105         // set owner
106         argn.ownerNode = ownerNode;
107         argn.isOwned(true);
108 
109         int i = findNamePoint(argn.getNodeName(),0);
110         AttrImpl previous = null;
111         if (i >= 0) {
112             previous = (AttrImpl) nodes.get(i);
113             nodes.set(i, arg);
114             previous.ownerNode = ownerNode.ownerDocument();
115             previous.isOwned(false);
116             // make sure it won't be mistaken with defaults in case it's reused
117             previous.isSpecified(true);
118         } else {
119             i = -1 - i; // Insert point (may be end of list)
120             if (null == nodes) {
121                 nodes = new ArrayList(5);
122             }
123             nodes.add(i, arg);
124         }
125 
126         // notify document
127         ownerNode.ownerDocument().setAttrNode(argn, previous);
128 
129         // If the new attribute is not normalized,
130         // the owning element is inherently not normalized.
131         if (!argn.isNormalized()) {
132             ownerNode.isNormalized(false);
133         }
134         return previous;
135 
136     } // setNamedItem(Node):Node
137 
138     /**
139      * Adds an attribute using its namespaceURI and localName.
140      * @see org.w3c.dom.NamedNodeMap#setNamedItem
141      * @return If the new Node replaces an existing node the replaced Node is
142      *      returned, otherwise null is returned.
143      * @param arg A node to store in a named node map.
144      */
145     public Node setNamedItemNS(Node arg)
146     throws DOMException {
147 
148         boolean errCheck = ownerNode.ownerDocument().errorChecking;
149         if (errCheck) {
150             if (isReadOnly()) {
151                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
152                 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
153             }
154             if(arg.getOwnerDocument() != ownerNode.ownerDocument()) {
155                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null);
156                 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, msg);
157             }
158             if (arg.getNodeType() != Node.ATTRIBUTE_NODE) {
159                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null);
160                 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, msg);
161             }
162         }
163         AttrImpl argn = (AttrImpl)arg;
164 
165         if (argn.isOwned()){
166             if (errCheck && argn.getOwnerElement() != ownerNode) {
167                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INUSE_ATTRIBUTE_ERR", null);
168                 throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, msg);
169             }
170             // replacing an Attribute with itself does nothing
171             return arg;
172         }
173 
174         // set owner
175         argn.ownerNode = ownerNode;
176         argn.isOwned(true);
177 
178         int i = findNamePoint(argn.getNamespaceURI(), argn.getLocalName());
179         AttrImpl previous = null;
180         if (i >= 0) {
181             previous = (AttrImpl) nodes.get(i);
182             nodes.set(i, arg);
183             previous.ownerNode = ownerNode.ownerDocument();
184             previous.isOwned(false);
185             // make sure it won't be mistaken with defaults in case it's reused
186             previous.isSpecified(true);
187         } else {
188             // If we can't find by namespaceURI, localName, then we find by
189             // nodeName so we know where to insert.
190             i = findNamePoint(arg.getNodeName(),0);
191             if (i >=0) {
192                 previous = (AttrImpl) nodes.get(i);
193                 nodes.add(i, arg);
194             } else {
195                 i = -1 - i; // Insert point (may be end of list)
196                 if (null == nodes) {
197                     nodes = new ArrayList(5);
198                 }
199                 nodes.add(i, arg);
200             }
201         }
202         //      changed(true);
203 
204         // notify document
205         ownerNode.ownerDocument().setAttrNode(argn, previous);
206 
207         // If the new attribute is not normalized,
208         // the owning element is inherently not normalized.
209         if (!argn.isNormalized()) {
210             ownerNode.isNormalized(false);
211         }
212         return previous;
213 
214     } // setNamedItemNS(Node):Node
215 
216     /**
217      * Removes an attribute specified by name.
218      * @param name
219      *      The name of a node to remove. If the
220      *      removed attribute is known to have a default value, an
221      *      attribute immediately appears containing the default value
222      *      as well as the corresponding namespace URI, local name,
223      *      and prefix when applicable.
224      * @return The node removed from the map if a node with such a name exists.
225      * @throws              NOT_FOUND_ERR: Raised if there is no node named
226      *                      name in the map.
227      */
228     /***/
229     public Node removeNamedItem(String name)
230         throws DOMException {
231         return internalRemoveNamedItem(name, true);
232     }
233 
234     /**
235      * Same as removeNamedItem except that it simply returns null if the
236      * specified name is not found.
237      */
238     Node safeRemoveNamedItem(String name) {
239         return internalRemoveNamedItem(name, false);
240     }
241 
242 
243     /**
244      * NON-DOM: Remove the node object
245      *
246      * NOTE: Specifically removes THIS NODE -- not the node with this
247      * name, nor the node with these contents. If node does not belong to
248      * this named node map, we throw a DOMException.
249      *
250      * @param item       The node to remove
251      * @param addDefault true -- magically add default attribute
252      * @return Removed node
253      * @exception DOMException
254      */
255     protected Node removeItem(Node item, boolean addDefault)
256         throws DOMException {
257 
258         int index = -1;
259         if (nodes != null) {
260             final int size = nodes.size();
261             for (int i = 0; i < size; ++i) {
262                 if (nodes.get(i) == item) {
263                     index = i;
264                     break;
265                 }
266             }
267         }
268         if (index < 0) {
269             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
270             throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
271         }
272 
273         return remove((AttrImpl)item, index, addDefault);
274     }
275 
276     /**
277      * Internal removeNamedItem method allowing to specify whether an exception
278      * must be thrown if the specified name is not found.
279      */
280     final protected Node internalRemoveNamedItem(String name, boolean raiseEx){
281         if (isReadOnly()) {
282                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
283                 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
284         }
285         int i = findNamePoint(name,0);
286         if (i < 0) {
287             if (raiseEx) {
288                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
289                 throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
290             } else {
291                 return null;
292             }
293         }
294 
295         return remove((AttrImpl)nodes.get(i), i, true);
296 
297     } // internalRemoveNamedItem(String,boolean):Node
298 
299     private final Node remove(AttrImpl attr, int index,
300                               boolean addDefault) {
301 
302         CoreDocumentImpl ownerDocument = ownerNode.ownerDocument();
303         String name = attr.getNodeName();
304         if (attr.isIdAttribute()) {
305             ownerDocument.removeIdentifier(attr.getValue());
306         }
307 
308         if (hasDefaults() && addDefault) {
309             // If there's a default, add it instead
310             NamedNodeMapImpl defaults =
311                 ((ElementImpl) ownerNode).getDefaultAttributes();
312 
313             Node d;
314             if (defaults != null &&
315                 (d = defaults.getNamedItem(name)) != null &&
316                 findNamePoint(name, index+1) < 0) {
317                     NodeImpl clone = (NodeImpl)d.cloneNode(true);
318                     if (d.getLocalName() !=null){
319                             // we must rely on the name to find a default attribute
320                             // ("test:attr"), but while copying it from the DOCTYPE
321                             // we should not loose namespace URI that was assigned
322                             // to the attribute in the instance document.
323                             ((AttrNSImpl)clone).namespaceURI = attr.getNamespaceURI();
324                     }
325                     clone.ownerNode = ownerNode;
326                     clone.isOwned(true);
327                     clone.isSpecified(false);
328 
329                     nodes.set(index, clone);
330                     if (attr.isIdAttribute()) {
331                         ownerDocument.putIdentifier(clone.getNodeValue(),
332                                                 (ElementImpl)ownerNode);
333                     }
334             } else {
335                 nodes.remove(index);
336             }
337         } else {
338             nodes.remove(index);
339         }
340 
341         //        changed(true);
342 
343         // remove reference to owner
344         attr.ownerNode = ownerDocument;
345         attr.isOwned(false);
346 
347         // make sure it won't be mistaken with defaults in case it's
348         // reused
349         attr.isSpecified(true);
350         attr.isIdAttribute(false);
351 
352         // notify document
353         ownerDocument.removedAttrNode(attr, ownerNode, name);
354 
355         return attr;
356     }
357 
358     /**
359      * Introduced in DOM Level 2. <p>
360      * Removes an attribute specified by local name and namespace URI.
361      * @param namespaceURI
362      *                      The namespace URI of the node to remove.
363      *                      When it is null or an empty string, this
364      *                      method behaves like removeNamedItem.
365      * @param name          The local name of the node to remove. If the
366      *                      removed attribute is known to have a default
367      *                      value, an attribute immediately appears
368      *                      containing the default value.
369      * @return Node         The node removed from the map if a node with such
370      *                      a local name and namespace URI exists.
371      * @throws              NOT_FOUND_ERR: Raised if there is no node named
372      *                      name in the map.
373      */
374     public Node removeNamedItemNS(String namespaceURI, String name)
375         throws DOMException {
376         return internalRemoveNamedItemNS(namespaceURI, name, true);
377     }
378 
379     /**
380      * Same as removeNamedItem except that it simply returns null if the
381      * specified local name and namespace URI is not found.
382      */
383     Node safeRemoveNamedItemNS(String namespaceURI, String name) {
384         return internalRemoveNamedItemNS(namespaceURI, name, false);
385     }
386 
387     /**
388      * Internal removeNamedItemNS method allowing to specify whether an
389      * exception must be thrown if the specified local name and namespace URI
390      * is not found.
391      */
392     final protected Node internalRemoveNamedItemNS(String namespaceURI,
393             String name,
394             boolean raiseEx) {
395 
396         CoreDocumentImpl ownerDocument = ownerNode.ownerDocument();
397         if (ownerDocument.errorChecking && isReadOnly()) {
398             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
399             throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
400         }
401         int i = findNamePoint(namespaceURI, name);
402         if (i < 0) {
403             if (raiseEx) {
404                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR", null);
405                 throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
406             } else {
407                 return null;
408             }
409         }
410 
411         AttrImpl n = (AttrImpl)nodes.get(i);
412 
413         if (n.isIdAttribute()) {
414             ownerDocument.removeIdentifier(n.getValue());
415         }
416         // If there's a default, add it instead
417         String nodeName = n.getNodeName();
418         if (hasDefaults()) {
419             NamedNodeMapImpl defaults = ((ElementImpl) ownerNode).getDefaultAttributes();
420             Node d;
421             if (defaults != null
422                     && (d = defaults.getNamedItem(nodeName)) != null)
423             {
424                 int j = findNamePoint(nodeName,0);
425                 if (j>=0 && findNamePoint(nodeName, j+1) < 0) {
426                     NodeImpl clone = (NodeImpl)d.cloneNode(true);
427                     clone.ownerNode = ownerNode;
428                     if (d.getLocalName() != null) {
429                         // we must rely on the name to find a default attribute
430                         // ("test:attr"), but while copying it from the DOCTYPE
431                         // we should not loose namespace URI that was assigned
432                         // to the attribute in the instance document.
433                         ((AttrNSImpl)clone).namespaceURI = namespaceURI;
434                     }
435                     clone.isOwned(true);
436                     clone.isSpecified(false);
437                     nodes.set(i, clone);
438                     if (clone.isIdAttribute()) {
439                         ownerDocument.putIdentifier(clone.getNodeValue(),
440                                 (ElementImpl)ownerNode);
441                     }
442                 } else {
443                     nodes.remove(i);
444                 }
445             } else {
446                 nodes.remove(i);
447             }
448         } else {
449             nodes.remove(i);
450         }
451 
452         //        changed(true);
453 
454         // remove reference to owner
455         n.ownerNode = ownerDocument;
456         n.isOwned(false);
457         // make sure it won't be mistaken with defaults in case it's
458         // reused
459         n.isSpecified(true);
460         // update id table if needed
461         n.isIdAttribute(false);
462 
463         // notify document
464         ownerDocument.removedAttrNode(n, ownerNode, name);
465 
466         return n;
467 
468     } // internalRemoveNamedItemNS(String,String,boolean):Node
469 
470     //
471     // Public methods
472     //
473 
474     /**
475      * Cloning a NamedNodeMap is a DEEP OPERATION; it always clones
476      * all the nodes contained in the map.
477      */
478 
479     public NamedNodeMapImpl cloneMap(NodeImpl ownerNode) {
480         AttributeMap newmap =
481             new AttributeMap((ElementImpl) ownerNode, null);
482         newmap.hasDefaults(hasDefaults());
483         newmap.cloneContent(this);
484         return newmap;
485     } // cloneMap():AttributeMap
486 
487     /**
488      * Override parent's method to set the ownerNode correctly
489      */
490     protected void cloneContent(NamedNodeMapImpl srcmap) {
491         List srcnodes = srcmap.nodes;
492         if (srcnodes != null) {
493             int size = srcnodes.size();
494             if (size != 0) {
495                 if (nodes == null) {
496                     nodes = new ArrayList(size);
497                 }
498                 else {
499                     nodes.clear();
500                 }
501                 for (int i = 0; i < size; ++i) {
502                     NodeImpl n = (NodeImpl) srcnodes.get(i);
503                     NodeImpl clone = (NodeImpl) n.cloneNode(true);
504                     clone.isSpecified(n.isSpecified());
505                     nodes.add(clone);
506                     clone.ownerNode = ownerNode;
507                     clone.isOwned(true);
508                 }
509             }
510         }
511     } // cloneContent():AttributeMap
512 
513 
514     /**
515      * Move specified attributes from the given map to this one
516      */
517     void moveSpecifiedAttributes(AttributeMap srcmap) {
518         int nsize = (srcmap.nodes != null) ? srcmap.nodes.size() : 0;
519         for (int i = nsize - 1; i >= 0; i--) {
520             AttrImpl attr = (AttrImpl) srcmap.nodes.get(i);
521             if (attr.isSpecified()) {
522                 srcmap.remove(attr, i, false);
523                 if (attr.getLocalName() != null) {
524                     setNamedItem(attr);
525                 }
526                 else {
527                     setNamedItemNS(attr);
528                 }
529             }
530         }
531     } // moveSpecifiedAttributes(AttributeMap):void
532 
533 
534     /**
535      * Get this AttributeMap in sync with the given "defaults" map.
536      * @param defaults The default attributes map to sync with.
537      */
538     protected void reconcileDefaults(NamedNodeMapImpl defaults) {
539 
540         // remove any existing default
541         int nsize = (nodes != null) ? nodes.size() : 0;
542         for (int i = nsize - 1; i >= 0; --i) {
543             AttrImpl attr = (AttrImpl) nodes.get(i);
544             if (!attr.isSpecified()) {
545                 remove(attr, i, false);
546             }
547         }
548         // add the new defaults
549         if (defaults == null) {
550             return;
551         }
552         if (nodes == null || nodes.size() == 0) {
553             cloneContent(defaults);
554         }
555         else {
556             int dsize = defaults.nodes.size();
557             for (int n = 0; n < dsize; ++n) {
558                 AttrImpl d = (AttrImpl) defaults.nodes.get(n);
559                 int i = findNamePoint(d.getNodeName(), 0);
560                 if (i < 0) {
561                         i = -1 - i;
562                     NodeImpl clone = (NodeImpl) d.cloneNode(true);
563                     clone.ownerNode = ownerNode;
564                     clone.isOwned(true);
565                     clone.isSpecified(false);
566                         nodes.add(i, clone);
567                 }
568             }
569         }
570 
571     } // reconcileDefaults()
572 
573     protected final int addItem (Node arg) {
574 
575         final AttrImpl argn = (AttrImpl) arg;
576 
577         // set owner
578         argn.ownerNode = ownerNode;
579         argn.isOwned(true);
580 
581         int i = findNamePoint(argn.getNamespaceURI(), argn.getLocalName());
582         if (i >= 0) {
583             nodes.set(i, arg);
584         }
585         else {
586             // If we can't find by namespaceURI, localName, then we find by
587             // nodeName so we know where to insert.
588             i = findNamePoint(argn.getNodeName(),0);
589             if (i >= 0) {
590                 nodes.add(i, arg);
591             }
592             else {
593                 i = -1 - i; // Insert point (may be end of list)
594                 if (null == nodes) {
595                     nodes = new ArrayList(5);
596                 }
597                 nodes.add(i, arg);
598             }
599         }
600 
601         // notify document
602         ownerNode.ownerDocument().setAttrNode(argn, null);
603         return i;
604     }
605 
606 } // class AttributeMap