001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.xerces.dom;
019:
020: import java.util.Vector;
021:
022: import org.w3c.dom.DOMException;
023: import org.w3c.dom.Node;
024:
025: /**
026: * AttributeMap inherits from NamedNodeMapImpl and extends it to deal with the
027: * specifics of storing attributes. These are:
028: * <ul>
029: * <li>managing ownership of attribute nodes
030: * <li>managing default attributes
031: * <li>firing mutation events
032: * </ul>
033: * <p>
034: * This class doesn't directly support mutation events, however, it notifies
035: * the document when mutations are performed so that the document class do so.
036: *
037: * @xerces.internal
038: *
039: * @version $Id: AttributeMap.java 449328 2006-09-23 22:58:23Z mrglavas $
040: */
041: public class AttributeMap extends NamedNodeMapImpl {
042:
043: /** Serialization version. */
044: static final long serialVersionUID = 8872606282138665383L;
045:
046: //
047: // Constructors
048: //
049:
050: /** Constructs a named node map. */
051: protected AttributeMap(ElementImpl ownerNode,
052: NamedNodeMapImpl defaults) {
053: super (ownerNode);
054: if (defaults != null) {
055: // initialize map with the defaults
056: cloneContent(defaults);
057: if (nodes != null) {
058: hasDefaults(true);
059: }
060: }
061: }
062:
063: /**
064: * Adds an attribute using its nodeName attribute.
065: * @see org.w3c.dom.NamedNodeMap#setNamedItem
066: * @return If the new Node replaces an existing node the replaced Node is
067: * returned, otherwise null is returned.
068: * @param arg
069: * An Attr node to store in this map.
070: * @exception org.w3c.dom.DOMException The exception description.
071: */
072: public Node setNamedItem(Node arg) throws DOMException {
073:
074: boolean errCheck = ownerNode.ownerDocument().errorChecking;
075: if (errCheck) {
076: if (isReadOnly()) {
077: String msg = DOMMessageFormatter.formatMessage(
078: DOMMessageFormatter.DOM_DOMAIN,
079: "NO_MODIFICATION_ALLOWED_ERR", null);
080: throw new DOMException(
081: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
082: }
083: if (arg.getOwnerDocument() != ownerNode.ownerDocument()) {
084: String msg = DOMMessageFormatter.formatMessage(
085: DOMMessageFormatter.DOM_DOMAIN,
086: "WRONG_DOCUMENT_ERR", null);
087: throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
088: msg);
089: }
090: if (arg.getNodeType() != Node.ATTRIBUTE_NODE) {
091: String msg = DOMMessageFormatter.formatMessage(
092: DOMMessageFormatter.DOM_DOMAIN,
093: "HIERARCHY_REQUEST_ERR", null);
094: throw new DOMException(
095: DOMException.HIERARCHY_REQUEST_ERR, msg);
096: }
097: }
098: AttrImpl argn = (AttrImpl) arg;
099:
100: if (argn.isOwned()) {
101: if (errCheck && argn.getOwnerElement() != ownerNode) {
102: String msg = DOMMessageFormatter.formatMessage(
103: DOMMessageFormatter.DOM_DOMAIN,
104: "INUSE_ATTRIBUTE_ERR", null);
105: throw new DOMException(
106: DOMException.INUSE_ATTRIBUTE_ERR, msg);
107: }
108: // replacing an Attribute with itself does nothing
109: return arg;
110: }
111:
112: // set owner
113: argn.ownerNode = ownerNode;
114: argn.isOwned(true);
115:
116: int i = findNamePoint(argn.getNodeName(), 0);
117: AttrImpl previous = null;
118: if (i >= 0) {
119: previous = (AttrImpl) nodes.elementAt(i);
120: nodes.setElementAt(arg, i);
121: previous.ownerNode = ownerNode.ownerDocument();
122: previous.isOwned(false);
123: // make sure it won't be mistaken with defaults in case it's reused
124: previous.isSpecified(true);
125: } else {
126: i = -1 - i; // Insert point (may be end of list)
127: if (null == nodes) {
128: nodes = new Vector(5, 10);
129: }
130: nodes.insertElementAt(arg, i);
131: }
132:
133: // notify document
134: ownerNode.ownerDocument().setAttrNode(argn, previous);
135:
136: // If the new attribute is not normalized,
137: // the owning element is inherently not normalized.
138: if (!argn.isNormalized()) {
139: ownerNode.isNormalized(false);
140: }
141: return previous;
142:
143: } // setNamedItem(Node):Node
144:
145: /**
146: * Adds an attribute using its namespaceURI and localName.
147: * @see org.w3c.dom.NamedNodeMap#setNamedItem
148: * @return If the new Node replaces an existing node the replaced Node is
149: * returned, otherwise null is returned.
150: * @param arg A node to store in a named node map.
151: */
152: public Node setNamedItemNS(Node arg) throws DOMException {
153:
154: boolean errCheck = ownerNode.ownerDocument().errorChecking;
155: if (errCheck) {
156: if (isReadOnly()) {
157: String msg = DOMMessageFormatter.formatMessage(
158: DOMMessageFormatter.DOM_DOMAIN,
159: "NO_MODIFICATION_ALLOWED_ERR", null);
160: throw new DOMException(
161: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
162: }
163: if (arg.getOwnerDocument() != ownerNode.ownerDocument()) {
164: String msg = DOMMessageFormatter.formatMessage(
165: DOMMessageFormatter.DOM_DOMAIN,
166: "WRONG_DOCUMENT_ERR", null);
167: throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
168: msg);
169: }
170: if (arg.getNodeType() != Node.ATTRIBUTE_NODE) {
171: String msg = DOMMessageFormatter.formatMessage(
172: DOMMessageFormatter.DOM_DOMAIN,
173: "HIERARCHY_REQUEST_ERR", null);
174: throw new DOMException(
175: DOMException.HIERARCHY_REQUEST_ERR, msg);
176: }
177: }
178: AttrImpl argn = (AttrImpl) arg;
179:
180: if (argn.isOwned()) {
181: if (errCheck && argn.getOwnerElement() != ownerNode) {
182: String msg = DOMMessageFormatter.formatMessage(
183: DOMMessageFormatter.DOM_DOMAIN,
184: "INUSE_ATTRIBUTE_ERR", null);
185: throw new DOMException(
186: DOMException.INUSE_ATTRIBUTE_ERR, msg);
187: }
188: // replacing an Attribute with itself does nothing
189: return arg;
190: }
191:
192: // set owner
193: argn.ownerNode = ownerNode;
194: argn.isOwned(true);
195:
196: int i = findNamePoint(argn.getNamespaceURI(), argn
197: .getLocalName());
198: AttrImpl previous = null;
199: if (i >= 0) {
200: previous = (AttrImpl) nodes.elementAt(i);
201: nodes.setElementAt(arg, i);
202: previous.ownerNode = ownerNode.ownerDocument();
203: previous.isOwned(false);
204: // make sure it won't be mistaken with defaults in case it's reused
205: previous.isSpecified(true);
206: } else {
207: // If we can't find by namespaceURI, localName, then we find by
208: // nodeName so we know where to insert.
209: i = findNamePoint(arg.getNodeName(), 0);
210: if (i >= 0) {
211: previous = (AttrImpl) nodes.elementAt(i);
212: nodes.insertElementAt(arg, i);
213: } else {
214: i = -1 - i; // Insert point (may be end of list)
215: if (null == nodes) {
216: nodes = new Vector(5, 10);
217: }
218: nodes.insertElementAt(arg, i);
219: }
220: }
221: // changed(true);
222:
223: // notify document
224: ownerNode.ownerDocument().setAttrNode(argn, previous);
225:
226: // If the new attribute is not normalized,
227: // the owning element is inherently not normalized.
228: if (!argn.isNormalized()) {
229: ownerNode.isNormalized(false);
230: }
231: return previous;
232:
233: } // setNamedItemNS(Node):Node
234:
235: /**
236: * Removes an attribute specified by name.
237: * @param name
238: * The name of a node to remove. If the
239: * removed attribute is known to have a default value, an
240: * attribute immediately appears containing the default value
241: * as well as the corresponding namespace URI, local name,
242: * and prefix when applicable.
243: * @return The node removed from the map if a node with such a name exists.
244: * @throws NOT_FOUND_ERR: Raised if there is no node named
245: * name in the map.
246: */
247: /***/
248: public Node removeNamedItem(String name) throws DOMException {
249: return internalRemoveNamedItem(name, true);
250: }
251:
252: /**
253: * Same as removeNamedItem except that it simply returns null if the
254: * specified name is not found.
255: */
256: Node safeRemoveNamedItem(String name) {
257: return internalRemoveNamedItem(name, false);
258: }
259:
260: /**
261: * NON-DOM: Remove the node object
262: *
263: * NOTE: Specifically removes THIS NODE -- not the node with this
264: * name, nor the node with these contents. If node does not belong to
265: * this named node map, we throw a DOMException.
266: *
267: * @param item The node to remove
268: * @param addDefault true -- magically add default attribute
269: * @return Removed node
270: * @exception DOMException
271: */
272: protected Node removeItem(Node item, boolean addDefault)
273: throws DOMException {
274:
275: int index = -1;
276: if (nodes != null) {
277: for (int i = 0; i < nodes.size(); i++) {
278: if (nodes.elementAt(i) == item) {
279: index = i;
280: break;
281: }
282: }
283: }
284: if (index < 0) {
285: String msg = DOMMessageFormatter.formatMessage(
286: DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR",
287: null);
288: throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
289: }
290:
291: return remove((AttrImpl) item, index, addDefault);
292: }
293:
294: /**
295: * Internal removeNamedItem method allowing to specify whether an exception
296: * must be thrown if the specified name is not found.
297: */
298: final protected Node internalRemoveNamedItem(String name,
299: boolean raiseEx) {
300: if (isReadOnly()) {
301: String msg = DOMMessageFormatter.formatMessage(
302: DOMMessageFormatter.DOM_DOMAIN,
303: "NO_MODIFICATION_ALLOWED_ERR", null);
304: throw new DOMException(
305: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
306: }
307: int i = findNamePoint(name, 0);
308: if (i < 0) {
309: if (raiseEx) {
310: String msg = DOMMessageFormatter.formatMessage(
311: DOMMessageFormatter.DOM_DOMAIN,
312: "NOT_FOUND_ERR", null);
313: throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
314: } else {
315: return null;
316: }
317: }
318:
319: return remove((AttrImpl) nodes.elementAt(i), i, true);
320:
321: } // internalRemoveNamedItem(String,boolean):Node
322:
323: private final Node remove(AttrImpl attr, int index,
324: boolean addDefault) {
325:
326: CoreDocumentImpl ownerDocument = ownerNode.ownerDocument();
327: String name = attr.getNodeName();
328: if (attr.isIdAttribute()) {
329: ownerDocument.removeIdentifier(attr.getValue());
330: }
331:
332: if (hasDefaults() && addDefault) {
333: // If there's a default, add it instead
334: NamedNodeMapImpl defaults = ((ElementImpl) ownerNode)
335: .getDefaultAttributes();
336:
337: Node d;
338: if (defaults != null
339: && (d = defaults.getNamedItem(name)) != null
340: && findNamePoint(name, index + 1) < 0) {
341: NodeImpl clone = (NodeImpl) d.cloneNode(true);
342: if (d.getLocalName() != null) {
343: // we must rely on the name to find a default attribute
344: // ("test:attr"), but while copying it from the DOCTYPE
345: // we should not loose namespace URI that was assigned
346: // to the attribute in the instance document.
347: ((AttrNSImpl) clone).namespaceURI = attr
348: .getNamespaceURI();
349: }
350: clone.ownerNode = ownerNode;
351: clone.isOwned(true);
352: clone.isSpecified(false);
353:
354: nodes.setElementAt(clone, index);
355: if (attr.isIdAttribute()) {
356: ownerDocument.putIdentifier(clone.getNodeValue(),
357: (ElementImpl) ownerNode);
358: }
359: } else {
360: nodes.removeElementAt(index);
361: }
362: } else {
363: nodes.removeElementAt(index);
364: }
365:
366: // changed(true);
367:
368: // remove reference to owner
369: attr.ownerNode = ownerDocument;
370: attr.isOwned(false);
371:
372: // make sure it won't be mistaken with defaults in case it's
373: // reused
374: attr.isSpecified(true);
375: attr.isIdAttribute(false);
376:
377: // notify document
378: ownerDocument.removedAttrNode(attr, ownerNode, name);
379:
380: return attr;
381: }
382:
383: /**
384: * Introduced in DOM Level 2. <p>
385: * Removes an attribute specified by local name and namespace URI.
386: * @param namespaceURI
387: * The namespace URI of the node to remove.
388: * When it is null or an empty string, this
389: * method behaves like removeNamedItem.
390: * @param name The local name of the node to remove. If the
391: * removed attribute is known to have a default
392: * value, an attribute immediately appears
393: * containing the default value.
394: * @return Node The node removed from the map if a node with such
395: * a local name and namespace URI exists.
396: * @throws NOT_FOUND_ERR: Raised if there is no node named
397: * name in the map.
398: */
399: public Node removeNamedItemNS(String namespaceURI, String name)
400: throws DOMException {
401: return internalRemoveNamedItemNS(namespaceURI, name, true);
402: }
403:
404: /**
405: * Same as removeNamedItem except that it simply returns null if the
406: * specified local name and namespace URI is not found.
407: */
408: Node safeRemoveNamedItemNS(String namespaceURI, String name) {
409: return internalRemoveNamedItemNS(namespaceURI, name, false);
410: }
411:
412: /**
413: * Internal removeNamedItemNS method allowing to specify whether an
414: * exception must be thrown if the specified local name and namespace URI
415: * is not found.
416: */
417: final protected Node internalRemoveNamedItemNS(String namespaceURI,
418: String name, boolean raiseEx) {
419:
420: CoreDocumentImpl ownerDocument = ownerNode.ownerDocument();
421: if (ownerDocument.errorChecking && isReadOnly()) {
422: String msg = DOMMessageFormatter.formatMessage(
423: DOMMessageFormatter.DOM_DOMAIN,
424: "NO_MODIFICATION_ALLOWED_ERR", null);
425: throw new DOMException(
426: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
427: }
428: int i = findNamePoint(namespaceURI, name);
429: if (i < 0) {
430: if (raiseEx) {
431: String msg = DOMMessageFormatter.formatMessage(
432: DOMMessageFormatter.DOM_DOMAIN,
433: "NOT_FOUND_ERR", null);
434: throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
435: } else {
436: return null;
437: }
438: }
439:
440: AttrImpl n = (AttrImpl) nodes.elementAt(i);
441:
442: if (n.isIdAttribute()) {
443: ownerDocument.removeIdentifier(n.getValue());
444: }
445: // If there's a default, add it instead
446: String nodeName = n.getNodeName();
447: if (hasDefaults()) {
448: NamedNodeMapImpl defaults = ((ElementImpl) ownerNode)
449: .getDefaultAttributes();
450: Node d;
451: if (defaults != null
452: && (d = defaults.getNamedItem(nodeName)) != null) {
453: int j = findNamePoint(nodeName, 0);
454: if (j >= 0 && findNamePoint(nodeName, j + 1) < 0) {
455: NodeImpl clone = (NodeImpl) d.cloneNode(true);
456: clone.ownerNode = ownerNode;
457: if (d.getLocalName() != null) {
458: // we must rely on the name to find a default attribute
459: // ("test:attr"), but while copying it from the DOCTYPE
460: // we should not loose namespace URI that was assigned
461: // to the attribute in the instance document.
462: ((AttrNSImpl) clone).namespaceURI = namespaceURI;
463: }
464: clone.isOwned(true);
465: clone.isSpecified(false);
466: nodes.setElementAt(clone, i);
467: if (clone.isIdAttribute()) {
468: ownerDocument.putIdentifier(clone
469: .getNodeValue(),
470: (ElementImpl) ownerNode);
471: }
472: } else {
473: nodes.removeElementAt(i);
474: }
475: } else {
476: nodes.removeElementAt(i);
477: }
478: } else {
479: nodes.removeElementAt(i);
480: }
481:
482: // changed(true);
483:
484: // remove reference to owner
485: n.ownerNode = ownerDocument;
486: n.isOwned(false);
487: // make sure it won't be mistaken with defaults in case it's
488: // reused
489: n.isSpecified(true);
490: // update id table if needed
491: n.isIdAttribute(false);
492:
493: // notify document
494: ownerDocument.removedAttrNode(n, ownerNode, name);
495:
496: return n;
497:
498: } // internalRemoveNamedItemNS(String,String,boolean):Node
499:
500: //
501: // Public methods
502: //
503:
504: /**
505: * Cloning a NamedNodeMap is a DEEP OPERATION; it always clones
506: * all the nodes contained in the map.
507: */
508:
509: public NamedNodeMapImpl cloneMap(NodeImpl ownerNode) {
510: AttributeMap newmap = new AttributeMap((ElementImpl) ownerNode,
511: null);
512: newmap.hasDefaults(hasDefaults());
513: newmap.cloneContent(this );
514: return newmap;
515: } // cloneMap():AttributeMap
516:
517: /**
518: * Override parent's method to set the ownerNode correctly
519: */
520: protected void cloneContent(NamedNodeMapImpl srcmap) {
521: Vector srcnodes = srcmap.nodes;
522: if (srcnodes != null) {
523: int size = srcnodes.size();
524: if (size != 0) {
525: if (nodes == null) {
526: nodes = new Vector(size);
527: }
528: nodes.setSize(size);
529: for (int i = 0; i < size; ++i) {
530: NodeImpl n = (NodeImpl) srcnodes.elementAt(i);
531: NodeImpl clone = (NodeImpl) n.cloneNode(true);
532: clone.isSpecified(n.isSpecified());
533: nodes.setElementAt(clone, i);
534: clone.ownerNode = ownerNode;
535: clone.isOwned(true);
536: }
537: }
538: }
539: } // cloneContent():AttributeMap
540:
541: /**
542: * Move specified attributes from the given map to this one
543: */
544: void moveSpecifiedAttributes(AttributeMap srcmap) {
545: int nsize = (srcmap.nodes != null) ? srcmap.nodes.size() : 0;
546: for (int i = nsize - 1; i >= 0; i--) {
547: AttrImpl attr = (AttrImpl) srcmap.nodes.elementAt(i);
548: if (attr.isSpecified()) {
549: srcmap.remove(attr, i, false);
550: if (attr.getLocalName() != null) {
551: setNamedItem(attr);
552: } else {
553: setNamedItemNS(attr);
554: }
555: }
556: }
557: } // moveSpecifiedAttributes(AttributeMap):void
558:
559: /**
560: * Get this AttributeMap in sync with the given "defaults" map.
561: * @param defaults The default attributes map to sync with.
562: */
563: protected void reconcileDefaults(NamedNodeMapImpl defaults) {
564:
565: // remove any existing default
566: int nsize = (nodes != null) ? nodes.size() : 0;
567: for (int i = nsize - 1; i >= 0; i--) {
568: AttrImpl attr = (AttrImpl) nodes.elementAt(i);
569: if (!attr.isSpecified()) {
570: remove(attr, i, false);
571: }
572: }
573: // add the new defaults
574: if (defaults == null) {
575: return;
576: }
577: if (nodes == null || nodes.size() == 0) {
578: cloneContent(defaults);
579: } else {
580: int dsize = defaults.nodes.size();
581: for (int n = 0; n < dsize; n++) {
582: AttrImpl d = (AttrImpl) defaults.nodes.elementAt(n);
583: int i = findNamePoint(d.getNodeName(), 0);
584: if (i < 0) {
585: i = -1 - i;
586: NodeImpl clone = (NodeImpl) d.cloneNode(true);
587: clone.ownerNode = ownerNode;
588: clone.isOwned(true);
589: clone.isSpecified(false);
590: nodes.insertElementAt(clone, i);
591: }
592: }
593: }
594:
595: } // reconcileDefaults()
596:
597: protected final int addItem(Node arg) {
598:
599: final AttrImpl argn = (AttrImpl) arg;
600:
601: // set owner
602: argn.ownerNode = ownerNode;
603: argn.isOwned(true);
604:
605: int i = findNamePoint(argn.getNamespaceURI(), argn
606: .getLocalName());
607: if (i >= 0) {
608: nodes.setElementAt(arg, i);
609: } else {
610: // If we can't find by namespaceURI, localName, then we find by
611: // nodeName so we know where to insert.
612: i = findNamePoint(argn.getNodeName(), 0);
613: if (i >= 0) {
614: nodes.insertElementAt(arg, i);
615: } else {
616: i = -1 - i; // Insert point (may be end of list)
617: if (null == nodes) {
618: nodes = new Vector(5, 10);
619: }
620: nodes.insertElementAt(arg, i);
621: }
622: }
623:
624: // notify document
625: ownerNode.ownerDocument().setAttrNode(argn, null);
626: return i;
627: }
628:
629: } // class AttributeMap
|