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