001: /*
002: * The Apache Software License, Version 1.1
003: *
004: *
005: * Copyright (c) 1999 The Apache Software Foundation. All rights
006: * reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * 1. Redistributions of source code must retain the above copyright
013: * notice, this list of conditions and the following disclaimer.
014: *
015: * 2. Redistributions in binary form must reproduce the above copyright
016: * notice, this list of conditions and the following disclaimer in
017: * the documentation and/or other materials provided with the
018: * distribution.
019: *
020: * 3. The end-user documentation included with the redistribution,
021: * if any, must include the following acknowledgment:
022: * "This product includes software developed by the
023: * Apache Software Foundation (http://www.apache.org/)."
024: * Alternately, this acknowledgment may appear in the software itself,
025: * if and wherever such third-party acknowledgments normally appear.
026: *
027: * 4. The names "Xerces" and "Apache Software Foundation" must
028: * not be used to endorse or promote products derived from this
029: * software without prior written permission. For written
030: * permission, please contact apache@apache.org.
031: *
032: * 5. Products derived from this software may not be called "Apache",
033: * nor may "Apache" appear in their name, without prior written
034: * permission of the Apache Software Foundation.
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
040: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: * ====================================================================
049: *
050: * This software consists of voluntary contributions made by many
051: * individuals on behalf of the Apache Software Foundation and was
052: * originally based on software copyright (c) 1999, International
053: * Business Machines, Inc., http://www.apache.org. For more
054: * information on the Apache Software Foundation, please see
055: * <http://www.apache.org/>.
056: */
057:
058: package org.apache.xerces.dom;
059:
060: import java.io.Serializable;
061: import java.util.Vector;
062: import java.util.Enumeration;
063:
064: import org.w3c.dom.DOMException;
065: import org.w3c.dom.NamedNodeMap;
066: import org.w3c.dom.Node;
067:
068: /**
069: * NamedNodeMaps represent collections of Nodes that can be accessed
070: * by name. Entity and Notation nodes are stored in NamedNodeMaps
071: * attached to the DocumentType. Attributes are placed in a NamedNodeMap
072: * attached to the elem they're related too. However, because attributes
073: * require more work, such as firing mutation events, they are stored in
074: * a subclass of NamedNodeMapImpl.
075: * <P>
076: * Only one Node may be stored per name; attempting to
077: * store another will replace the previous value.
078: * <P>
079: * NOTE: The "primary" storage key is taken from the NodeName attribute of the
080: * node. The "secondary" storage key is the namespaceURI and localName, when
081: * accessed by DOM level 2 nodes. All nodes, even DOM Level 2 nodes are stored
082: * in a single Vector sorted by the primary "nodename" key.
083: * <P>
084: * NOTE: item()'s integer index does _not_ imply that the named nodes
085: * must be stored in an array; that's only an access method. Note too
086: * that these indices are "live"; if someone changes the map's
087: * contents, the indices associated with nodes may change.
088: * <P>
089: *
090: * @version
091: * @since PR-DOM-Level-1-19980818.
092: */
093: public class NamedNodeMapImpl implements NamedNodeMap, Serializable {
094:
095: //
096: // Constants
097: //
098:
099: /** Serialization version. */
100: static final long serialVersionUID = -7039242451046758020L;
101:
102: //
103: // Data
104: //
105:
106: protected short flags;
107:
108: protected final static short READONLY = 0x1 << 0;
109: protected final static short CHANGED = 0x1 << 1;
110: protected final static short HASDEFAULTS = 0x1 << 2;
111:
112: /** Nodes. */
113: protected Vector nodes;
114:
115: protected NodeImpl ownerNode; // the node this map belongs to
116:
117: //
118: // Constructors
119: //
120:
121: /** Constructs a named node map. */
122: protected NamedNodeMapImpl(NodeImpl ownerNode) {
123: this .ownerNode = ownerNode;
124: }
125:
126: //
127: // NamedNodeMap methods
128: //
129:
130: /**
131: * Report how many nodes are currently stored in this NamedNodeMap.
132: * Caveat: This is a count rather than an index, so the
133: * highest-numbered node at any time can be accessed via
134: * item(getLength()-1).
135: */
136: public int getLength() {
137: return (nodes != null) ? nodes.size() : 0;
138: }
139:
140: /**
141: * Retrieve an item from the map by 0-based index.
142: *
143: * @param index Which item to retrieve. Note that indices are just an
144: * enumeration of the current contents; they aren't guaranteed to be
145: * stable, nor do they imply any promises about the order of the
146: * NamedNodeMap's contents. In other words, DO NOT assume either that
147: * index(i) will always refer to the same entry, or that there is any
148: * stable ordering of entries... and be prepared for double-reporting
149: * or skips as insertion and deletion occur.
150: *
151: * @returns the node which currenly has the specified index, or null
152: * if index is greater than or equal to getLength().
153: */
154: public Node item(int index) {
155: return (nodes != null && index < nodes.size()) ? (Node) (nodes
156: .elementAt(index)) : null;
157: }
158:
159: /**
160: * Retrieve a node by name.
161: *
162: * @param name Name of a node to look up.
163: * @returns the Node (of unspecified sub-class) stored with that name,
164: * or null if no value has been assigned to that name.
165: */
166: public Node getNamedItem(String name) {
167:
168: int i = findNamePoint(name, 0);
169: return (i < 0) ? null : (Node) (nodes.elementAt(i));
170:
171: } // getNamedItem(String):Node
172:
173: /**
174: * Introduced in DOM Level 2. <p>
175: * Retrieves a node specified by local name and namespace URI.
176: *
177: * @param namespaceURI The namespace URI of the node to retrieve.
178: * When it is null or an empty string, this
179: * method behaves like getNamedItem.
180: * @param localName The local name of the node to retrieve.
181: * @return Node A Node (of any type) with the specified name, or null if the specified
182: * name did not identify any node in the map.
183: */
184: public Node getNamedItemNS(String namespaceURI, String localName) {
185:
186: int i = findNamePoint(namespaceURI, localName);
187: return (i < 0) ? null : (Node) (nodes.elementAt(i));
188:
189: } // getNamedItemNS(String,String):Node
190:
191: /**
192: * Adds a node using its nodeName attribute.
193: * As the nodeName attribute is used to derive the name which the node must be
194: * stored under, multiple nodes of certain types (those that have a "special" string
195: * value) cannot be stored as the names would clash. This is seen as preferable to
196: * allowing nodes to be aliased.
197: * @see org.w3c.dom.NamedNodeMap#setNamedItem
198: * @return If the new Node replaces an existing node the replaced Node is returned,
199: * otherwise null is returned.
200: * @param arg
201: * A node to store in a named node map. The node will later be
202: * accessible using the value of the namespaceURI and localName
203: * attribute of the node. If a node with those namespace URI and
204: * local name is already present in the map, it is replaced by the new
205: * one.
206: * @exception org.w3c.dom.DOMException The exception description.
207: */
208: public Node setNamedItem(Node arg) throws DOMException {
209:
210: if (isReadOnly()) {
211: throw new DOMException(
212: DOMException.NO_MODIFICATION_ALLOWED_ERR,
213: "DOM001 Modification not allowed");
214: }
215: if (arg.getOwnerDocument() != ownerNode.ownerDocument()) {
216: throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
217: "DOM005 Wrong document");
218: }
219:
220: int i = findNamePoint(arg.getNodeName(), 0);
221: NodeImpl previous = null;
222: if (i >= 0) {
223: previous = (NodeImpl) nodes.elementAt(i);
224: nodes.setElementAt(arg, i);
225: } else {
226: i = -1 - i; // Insert point (may be end of list)
227: if (null == nodes) {
228: nodes = new Vector(5, 10);
229: }
230: nodes.insertElementAt(arg, i);
231: }
232: return previous;
233:
234: } // setNamedItem(Node):Node
235:
236: /**
237: * Adds a node using its namespaceURI and localName.
238: * @see org.w3c.dom.NamedNodeMap#setNamedItem
239: * @return If the new Node replaces an existing node the replaced Node is returned,
240: * otherwise null is returned.
241: * @param arg A node to store in a named node map. The node will later be
242: * accessible using the value of the namespaceURI and localName
243: * attribute of the node. If a node with those namespace URI and
244: * local name is already present in the map, it is replaced by the new
245: * one.
246: */
247: public Node setNamedItemNS(Node arg) throws DOMException {
248:
249: if (isReadOnly()) {
250: throw new DOMException(
251: DOMException.NO_MODIFICATION_ALLOWED_ERR,
252: "DOM001 Modification not allowed");
253: }
254:
255: if (arg.getOwnerDocument() != ownerNode.ownerDocument()) {
256: throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
257: "DOM005 Wrong document");
258: }
259:
260: int i = findNamePoint(arg.getNamespaceURI(), arg.getLocalName());
261: NodeImpl previous = null;
262: if (i >= 0) {
263: previous = (NodeImpl) nodes.elementAt(i);
264: nodes.setElementAt(arg, i);
265: } else {
266: // If we can't find by namespaceURI, localName, then we find by
267: // nodeName so we know where to insert.
268: i = findNamePoint(arg.getNodeName(), 0);
269: if (i >= 0) {
270: previous = (NodeImpl) nodes.elementAt(i);
271: nodes.insertElementAt(arg, i);
272: } else {
273: i = -1 - i; // Insert point (may be end of list)
274: if (null == nodes) {
275: nodes = new Vector(5, 10);
276: }
277: nodes.insertElementAt(arg, i);
278: }
279: }
280: return previous;
281:
282: } // setNamedItem(Node):Node
283:
284: /**
285: * Removes a node specified by name.
286: * @param name The name of a node to remove.
287: * @return The node removed from the map if a node with such a name exists.
288: */
289: /***/
290: public Node removeNamedItem(String name) throws DOMException {
291:
292: if (isReadOnly()) {
293: throw new DOMException(
294: DOMException.NO_MODIFICATION_ALLOWED_ERR,
295: "DOM001 Modification not allowed");
296: }
297: int i = findNamePoint(name, 0);
298: if (i < 0) {
299: throw new DOMException(DOMException.NOT_FOUND_ERR,
300: "DOM008 Not found");
301: }
302:
303: NodeImpl n = (NodeImpl) nodes.elementAt(i);
304: nodes.removeElementAt(i);
305:
306: return n;
307:
308: } // removeNamedItem(String):Node
309:
310: /**
311: * Introduced in DOM Level 2. <p>
312: * Removes a node specified by local name and namespace URI.
313: * @param namespaceURI
314: * The namespace URI of the node to remove.
315: * When it is null or an empty string, this
316: * method behaves like removeNamedItem.
317: * @param The local name of the node to remove.
318: * @return Node The node removed from the map if a node with such
319: * a local name and namespace URI exists.
320: * @throws NOT_FOUND_ERR: Raised if there is no node named
321: * name in the map.
322:
323: */
324: public Node removeNamedItemNS(String namespaceURI, String name)
325: throws DOMException {
326:
327: if (isReadOnly()) {
328: throw new DOMException(
329: DOMException.NO_MODIFICATION_ALLOWED_ERR,
330: "DOM001 Modification not allowed");
331: }
332: int i = findNamePoint(namespaceURI, name);
333: if (i < 0) {
334: throw new DOMException(DOMException.NOT_FOUND_ERR,
335: "DOM008 Not found");
336: }
337:
338: NodeImpl n = (NodeImpl) nodes.elementAt(i);
339: nodes.removeElementAt(i);
340:
341: return n;
342:
343: } // removeNamedItem(String):Node
344:
345: //
346: // Public methods
347: //
348:
349: /**
350: * Cloning a NamedNodeMap is a DEEP OPERATION; it always clones
351: * all the nodes contained in the map.
352: */
353:
354: public NamedNodeMapImpl cloneMap(NodeImpl ownerNode) {
355: NamedNodeMapImpl newmap = new NamedNodeMapImpl(ownerNode);
356: newmap.cloneContent(this );
357: return newmap;
358: }
359:
360: protected void cloneContent(NamedNodeMapImpl srcmap) {
361: if (srcmap.nodes != null) {
362: nodes = new Vector(srcmap.nodes.size());
363: for (int i = 0; i < srcmap.nodes.size(); ++i) {
364: NodeImpl n = (NodeImpl) srcmap.nodes.elementAt(i);
365: NodeImpl clone = (NodeImpl) n.cloneNode(true);
366: clone.isSpecified(n.isSpecified());
367: nodes.insertElementAt(clone, i);
368: }
369: }
370: } // cloneMap():NamedNodeMapImpl
371:
372: //
373: // Package methods
374: //
375:
376: /**
377: * Internal subroutine to allow read-only Nodes to make their contained
378: * NamedNodeMaps readonly too. I expect that in fact the shallow
379: * version of this operation will never be
380: *
381: * @param readOnly boolean true to make read-only, false to permit editing.
382: * @param deep boolean true to pass this request along to the contained
383: * nodes, false to only toggle the NamedNodeMap itself. I expect that
384: * the shallow version of this operation will never be used, but I want
385: * to design it in now, while I'm thinking about it.
386: */
387: void setReadOnly(boolean readOnly, boolean deep) {
388:
389: isReadOnly(readOnly);
390: if (deep && nodes != null) {
391: Enumeration e = nodes.elements();
392: while (e.hasMoreElements()) {
393: ((NodeImpl) e.nextElement())
394: .setReadOnly(readOnly, deep);
395: }
396: }
397:
398: } // setReadOnly(boolean,boolean)
399:
400: /**
401: * Internal subroutine returns this NodeNameMap's (shallow) readOnly value.
402: *
403: */
404: boolean getReadOnly() {
405: return isReadOnly();
406: } // getReadOnly()
407:
408: //
409: // Protected methods
410: //
411:
412: /**
413: * NON-DOM
414: * set the ownerDocument of this node, and the attributes it contains
415: */
416: void setOwnerDocument(CoreDocumentImpl doc) {
417: if (nodes != null) {
418: for (int i = 0; i < nodes.size(); i++) {
419: ((NodeImpl) item(i)).setOwnerDocument(doc);
420: }
421: }
422: }
423:
424: final boolean isReadOnly() {
425: return (flags & READONLY) != 0;
426: }
427:
428: final void isReadOnly(boolean value) {
429: flags = (short) (value ? flags | READONLY : flags & ~READONLY);
430: }
431:
432: final boolean changed() {
433: return (flags & CHANGED) != 0;
434: }
435:
436: final void changed(boolean value) {
437: flags = (short) (value ? flags | CHANGED : flags & ~CHANGED);
438: }
439:
440: final boolean hasDefaults() {
441: return (flags & HASDEFAULTS) != 0;
442: }
443:
444: final void hasDefaults(boolean value) {
445: flags = (short) (value ? flags | HASDEFAULTS : flags
446: & ~HASDEFAULTS);
447: }
448:
449: //
450: // Private methods
451: //
452:
453: /**
454: * Subroutine: Locate the named item, or the point at which said item
455: * should be added.
456: *
457: * @param name Name of a node to look up.
458: *
459: * @return If positive or zero, the index of the found item.
460: * If negative, index of the appropriate point at which to insert
461: * the item, encoded as -1-index and hence reconvertable by subtracting
462: * it from -1. (Encoding because I don't want to recompare the strings
463: * but don't want to burn bytes on a datatype to hold a flagged value.)
464: */
465: protected int findNamePoint(String name, int start) {
466:
467: // Binary search
468: int i = 0;
469: if (nodes != null) {
470: int first = start;
471: int last = nodes.size() - 1;
472:
473: while (first <= last) {
474: i = (first + last) / 2;
475: int test = name.compareTo(((Node) (nodes.elementAt(i)))
476: .getNodeName());
477: if (test == 0) {
478: return i; // Name found
479: } else if (test < 0) {
480: last = i - 1;
481: } else {
482: first = i + 1;
483: }
484: }
485:
486: if (first > i) {
487: i = first;
488: }
489: }
490:
491: return -1 - i; // not-found has to be encoded.
492:
493: } // findNamePoint(String):int
494:
495: /** This findNamePoint is for DOM Level 2 Namespaces.
496: */
497: protected int findNamePoint(String namespaceURI, String name) {
498:
499: if (nodes == null)
500: return -1;
501: if (name == null)
502: return -1;
503:
504: // This is a linear search through the same nodes Vector.
505: // The Vector is sorted on the DOM Level 1 nodename.
506: // The DOM Level 2 NS keys are namespaceURI and Localname,
507: // so we must linear search thru it.
508: // In addition, to get this to work with nodes without any namespace
509: // (namespaceURI and localNames are both null) we then use the nodeName
510: // as a seconday key.
511: for (int i = 0; i < nodes.size(); i++) {
512: NodeImpl a = (NodeImpl) nodes.elementAt(i);
513: String aNamespaceURI = a.getNamespaceURI();
514: String aLocalName = a.getLocalName();
515: if (namespaceURI == null) {
516: if (aNamespaceURI == null
517: && (name.equals(aLocalName) || (aLocalName == null && name
518: .equals(a.getNodeName()))))
519: return i;
520: } else {
521: if (namespaceURI.equals(aNamespaceURI)
522: && name.equals(aLocalName))
523: return i;
524: }
525: }
526: return -1;
527: }
528:
529: } // class NamedNodeMapImpl
|