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.io.Serializable;
021: import java.util.Vector;
022:
023: import org.w3c.dom.DOMException;
024: import org.w3c.dom.NamedNodeMap;
025: import org.w3c.dom.Node;
026:
027: /**
028: * NamedNodeMaps represent collections of Nodes that can be accessed
029: * by name. Entity and Notation nodes are stored in NamedNodeMaps
030: * attached to the DocumentType. Attributes are placed in a NamedNodeMap
031: * attached to the elem they're related too. However, because attributes
032: * require more work, such as firing mutation events, they are stored in
033: * a subclass of NamedNodeMapImpl.
034: * <P>
035: * Only one Node may be stored per name; attempting to
036: * store another will replace the previous value.
037: * <P>
038: * NOTE: The "primary" storage key is taken from the NodeName attribute of the
039: * node. The "secondary" storage key is the namespaceURI and localName, when
040: * accessed by DOM level 2 nodes. All nodes, even DOM Level 2 nodes are stored
041: * in a single Vector sorted by the primary "nodename" key.
042: * <P>
043: * NOTE: item()'s integer index does _not_ imply that the named nodes
044: * must be stored in an array; that's only an access method. Note too
045: * that these indices are "live"; if someone changes the map's
046: * contents, the indices associated with nodes may change.
047: * <P>
048: *
049: * @xerces.internal
050: *
051: * @version $Id: NamedNodeMapImpl.java 449328 2006-09-23 22:58:23Z mrglavas $
052: * @since PR-DOM-Level-1-19980818.
053: */
054: public class NamedNodeMapImpl implements NamedNodeMap, Serializable {
055:
056: //
057: // Constants
058: //
059:
060: /** Serialization version. */
061: static final long serialVersionUID = -7039242451046758020L;
062:
063: //
064: // Data
065: //
066:
067: protected short flags;
068:
069: protected final static short READONLY = 0x1 << 0;
070: protected final static short CHANGED = 0x1 << 1;
071: protected final static short HASDEFAULTS = 0x1 << 2;
072:
073: /** Nodes. */
074: protected Vector nodes;
075:
076: protected NodeImpl ownerNode; // the node this map belongs to
077:
078: //
079: // Constructors
080: //
081:
082: /** Constructs a named node map. */
083: protected NamedNodeMapImpl(NodeImpl ownerNode) {
084: this .ownerNode = ownerNode;
085: }
086:
087: //
088: // NamedNodeMap methods
089: //
090:
091: /**
092: * Report how many nodes are currently stored in this NamedNodeMap.
093: * Caveat: This is a count rather than an index, so the
094: * highest-numbered node at any time can be accessed via
095: * item(getLength()-1).
096: */
097: public int getLength() {
098: return (nodes != null) ? nodes.size() : 0;
099: }
100:
101: /**
102: * Retrieve an item from the map by 0-based index.
103: *
104: * @param index Which item to retrieve. Note that indices are just an
105: * enumeration of the current contents; they aren't guaranteed to be
106: * stable, nor do they imply any promises about the order of the
107: * NamedNodeMap's contents. In other words, DO NOT assume either that
108: * index(i) will always refer to the same entry, or that there is any
109: * stable ordering of entries... and be prepared for double-reporting
110: * or skips as insertion and deletion occur.
111: *
112: * @return the node which currenly has the specified index, or null if index
113: * is greater than or equal to getLength().
114: */
115: public Node item(int index) {
116: return (nodes != null && index < nodes.size()) ? (Node) (nodes
117: .elementAt(index)) : null;
118: }
119:
120: /**
121: * Retrieve a node by name.
122: *
123: * @param name Name of a node to look up.
124: * @return the Node (of unspecified sub-class) stored with that name, or
125: * null if no value has been assigned to that name.
126: */
127: public Node getNamedItem(String name) {
128:
129: int i = findNamePoint(name, 0);
130: return (i < 0) ? null : (Node) (nodes.elementAt(i));
131:
132: } // getNamedItem(String):Node
133:
134: /**
135: * Introduced in DOM Level 2. <p>
136: * Retrieves a node specified by local name and namespace URI.
137: *
138: * @param namespaceURI The namespace URI of the node to retrieve.
139: * When it is null or an empty string, this
140: * method behaves like getNamedItem.
141: * @param localName The local name of the node to retrieve.
142: * @return Node A Node (of any type) with the specified name, or null if the specified
143: * name did not identify any node in the map.
144: */
145: public Node getNamedItemNS(String namespaceURI, String localName) {
146:
147: int i = findNamePoint(namespaceURI, localName);
148: return (i < 0) ? null : (Node) (nodes.elementAt(i));
149:
150: } // getNamedItemNS(String,String):Node
151:
152: /**
153: * Adds a node using its nodeName attribute.
154: * As the nodeName attribute is used to derive the name which the node must be
155: * stored under, multiple nodes of certain types (those that have a "special" string
156: * value) cannot be stored as the names would clash. This is seen as preferable to
157: * allowing nodes to be aliased.
158: * @see org.w3c.dom.NamedNodeMap#setNamedItem
159: * @return If the new Node replaces an existing node the replaced Node is returned,
160: * otherwise null is returned.
161: * @param arg
162: * A node to store in a named node map. The node will later be
163: * accessible using the value of the namespaceURI and localName
164: * attribute of the node. If a node with those namespace URI and
165: * local name is already present in the map, it is replaced by the new
166: * one.
167: * @exception org.w3c.dom.DOMException The exception description.
168: */
169: public Node setNamedItem(Node arg) throws DOMException {
170:
171: CoreDocumentImpl ownerDocument = ownerNode.ownerDocument();
172: if (ownerDocument.errorChecking) {
173: if (isReadOnly()) {
174: String msg = DOMMessageFormatter.formatMessage(
175: DOMMessageFormatter.DOM_DOMAIN,
176: "NO_MODIFICATION_ALLOWED_ERR", null);
177: throw new DOMException(
178: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
179: }
180: if (arg.getOwnerDocument() != ownerDocument) {
181: String msg = DOMMessageFormatter.formatMessage(
182: DOMMessageFormatter.DOM_DOMAIN,
183: "WRONG_DOCUMENT_ERR", null);
184: throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
185: msg);
186: }
187: }
188:
189: int i = findNamePoint(arg.getNodeName(), 0);
190: NodeImpl previous = null;
191: if (i >= 0) {
192: previous = (NodeImpl) nodes.elementAt(i);
193: nodes.setElementAt(arg, i);
194: } else {
195: i = -1 - i; // Insert point (may be end of list)
196: if (null == nodes) {
197: nodes = new Vector(5, 10);
198: }
199: nodes.insertElementAt(arg, i);
200: }
201: return previous;
202:
203: } // setNamedItem(Node):Node
204:
205: /**
206: * Adds a node using its namespaceURI and localName.
207: * @see org.w3c.dom.NamedNodeMap#setNamedItem
208: * @return If the new Node replaces an existing node the replaced Node is returned,
209: * otherwise null is returned.
210: * @param arg A node to store in a named node map. The node will later be
211: * accessible using the value of the namespaceURI and localName
212: * attribute of the node. If a node with those namespace URI and
213: * local name is already present in the map, it is replaced by the new
214: * one.
215: */
216: public Node setNamedItemNS(Node arg) throws DOMException {
217:
218: CoreDocumentImpl ownerDocument = ownerNode.ownerDocument();
219: if (ownerDocument.errorChecking) {
220: if (isReadOnly()) {
221: String msg = DOMMessageFormatter.formatMessage(
222: DOMMessageFormatter.DOM_DOMAIN,
223: "NO_MODIFICATION_ALLOWED_ERR", null);
224: throw new DOMException(
225: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
226: }
227:
228: if (arg.getOwnerDocument() != ownerDocument) {
229: String msg = DOMMessageFormatter.formatMessage(
230: DOMMessageFormatter.DOM_DOMAIN,
231: "WRONG_DOCUMENT_ERR", null);
232: throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
233: msg);
234: }
235: }
236:
237: int i = findNamePoint(arg.getNamespaceURI(), arg.getLocalName());
238: NodeImpl previous = null;
239: if (i >= 0) {
240: previous = (NodeImpl) nodes.elementAt(i);
241: nodes.setElementAt(arg, i);
242: } else {
243: // If we can't find by namespaceURI, localName, then we find by
244: // nodeName so we know where to insert.
245: i = findNamePoint(arg.getNodeName(), 0);
246: if (i >= 0) {
247: previous = (NodeImpl) nodes.elementAt(i);
248: nodes.insertElementAt(arg, i);
249: } else {
250: i = -1 - i; // Insert point (may be end of list)
251: if (null == nodes) {
252: nodes = new Vector(5, 10);
253: }
254: nodes.insertElementAt(arg, i);
255: }
256: }
257: return previous;
258:
259: } // setNamedItemNS(Node):Node
260:
261: /**
262: * Removes a node specified by name.
263: * @param name The name of a node to remove.
264: * @return The node removed from the map if a node with such a name exists.
265: */
266: /***/
267: public Node removeNamedItem(String name) throws DOMException {
268:
269: if (isReadOnly()) {
270: String msg = DOMMessageFormatter.formatMessage(
271: DOMMessageFormatter.DOM_DOMAIN,
272: "NO_MODIFICATION_ALLOWED_ERR", null);
273: throw new DOMException(
274: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
275: }
276: int i = findNamePoint(name, 0);
277: if (i < 0) {
278: String msg = DOMMessageFormatter.formatMessage(
279: DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR",
280: null);
281: throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
282: }
283:
284: NodeImpl n = (NodeImpl) nodes.elementAt(i);
285: nodes.removeElementAt(i);
286:
287: return n;
288:
289: } // removeNamedItem(String):Node
290:
291: /**
292: * Introduced in DOM Level 2. <p>
293: * Removes a node specified by local name and namespace URI.
294: * @param namespaceURI
295: * The namespace URI of the node to remove.
296: * When it is null or an empty string, this
297: * method behaves like removeNamedItem.
298: * @param name The local name of the node to remove.
299: * @return Node The node removed from the map if a node with such
300: * a local name and namespace URI exists.
301: * @throws NOT_FOUND_ERR: Raised if there is no node named
302: * name in the map.
303:
304: */
305: public Node removeNamedItemNS(String namespaceURI, String name)
306: throws DOMException {
307:
308: if (isReadOnly()) {
309: String msg = DOMMessageFormatter.formatMessage(
310: DOMMessageFormatter.DOM_DOMAIN,
311: "NO_MODIFICATION_ALLOWED_ERR", null);
312: throw new DOMException(
313: DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
314: }
315: int i = findNamePoint(namespaceURI, name);
316: if (i < 0) {
317: String msg = DOMMessageFormatter.formatMessage(
318: DOMMessageFormatter.DOM_DOMAIN, "NOT_FOUND_ERR",
319: null);
320: throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
321: }
322:
323: NodeImpl n = (NodeImpl) nodes.elementAt(i);
324: nodes.removeElementAt(i);
325:
326: return n;
327:
328: } // removeNamedItem(String):Node
329:
330: //
331: // Public methods
332: //
333:
334: /**
335: * Cloning a NamedNodeMap is a DEEP OPERATION; it always clones
336: * all the nodes contained in the map.
337: */
338:
339: public NamedNodeMapImpl cloneMap(NodeImpl ownerNode) {
340: NamedNodeMapImpl newmap = new NamedNodeMapImpl(ownerNode);
341: newmap.cloneContent(this );
342: return newmap;
343: }
344:
345: protected void cloneContent(NamedNodeMapImpl srcmap) {
346: Vector srcnodes = srcmap.nodes;
347: if (srcnodes != null) {
348: int size = srcnodes.size();
349: if (size != 0) {
350: if (nodes == null) {
351: nodes = new Vector(size);
352: }
353: nodes.setSize(size);
354: for (int i = 0; i < size; ++i) {
355: NodeImpl n = (NodeImpl) srcmap.nodes.elementAt(i);
356: NodeImpl clone = (NodeImpl) n.cloneNode(true);
357: clone.isSpecified(n.isSpecified());
358: nodes.setElementAt(clone, i);
359: }
360: }
361: }
362: } // cloneMap():NamedNodeMapImpl
363:
364: //
365: // Package methods
366: //
367:
368: /**
369: * Internal subroutine to allow read-only Nodes to make their contained
370: * NamedNodeMaps readonly too. I expect that in fact the shallow
371: * version of this operation will never be
372: *
373: * @param readOnly boolean true to make read-only, false to permit editing.
374: * @param deep boolean true to pass this request along to the contained
375: * nodes, false to only toggle the NamedNodeMap itself. I expect that
376: * the shallow version of this operation will never be used, but I want
377: * to design it in now, while I'm thinking about it.
378: */
379: void setReadOnly(boolean readOnly, boolean deep) {
380: isReadOnly(readOnly);
381: if (deep && nodes != null) {
382: for (int i = nodes.size() - 1; i >= 0; i--) {
383: ((NodeImpl) nodes.elementAt(i)).setReadOnly(readOnly,
384: deep);
385: }
386: }
387: } // setReadOnly(boolean,boolean)
388:
389: /**
390: * Internal subroutine returns this NodeNameMap's (shallow) readOnly value.
391: *
392: */
393: boolean getReadOnly() {
394: return isReadOnly();
395: } // getReadOnly()
396:
397: //
398: // Protected methods
399: //
400:
401: /**
402: * NON-DOM
403: * set the ownerDocument of this node, and the attributes it contains
404: */
405: protected void setOwnerDocument(CoreDocumentImpl doc) {
406: if (nodes != null) {
407: for (int i = 0; i < nodes.size(); i++) {
408: ((NodeImpl) item(i)).setOwnerDocument(doc);
409: }
410: }
411: }
412:
413: final boolean isReadOnly() {
414: return (flags & READONLY) != 0;
415: }
416:
417: final void isReadOnly(boolean value) {
418: flags = (short) (value ? flags | READONLY : flags & ~READONLY);
419: }
420:
421: final boolean changed() {
422: return (flags & CHANGED) != 0;
423: }
424:
425: final void changed(boolean value) {
426: flags = (short) (value ? flags | CHANGED : flags & ~CHANGED);
427: }
428:
429: final boolean hasDefaults() {
430: return (flags & HASDEFAULTS) != 0;
431: }
432:
433: final void hasDefaults(boolean value) {
434: flags = (short) (value ? flags | HASDEFAULTS : flags
435: & ~HASDEFAULTS);
436: }
437:
438: //
439: // Private methods
440: //
441:
442: /**
443: * Subroutine: Locate the named item, or the point at which said item
444: * should be added.
445: *
446: * @param name Name of a node to look up.
447: *
448: * @return If positive or zero, the index of the found item.
449: * If negative, index of the appropriate point at which to insert
450: * the item, encoded as -1-index and hence reconvertable by subtracting
451: * it from -1. (Encoding because I don't want to recompare the strings
452: * but don't want to burn bytes on a datatype to hold a flagged value.)
453: */
454: protected int findNamePoint(String name, int start) {
455:
456: // Binary search
457: int i = 0;
458: if (nodes != null) {
459: int first = start;
460: int last = nodes.size() - 1;
461:
462: while (first <= last) {
463: i = (first + last) / 2;
464: int test = name.compareTo(((Node) (nodes.elementAt(i)))
465: .getNodeName());
466: if (test == 0) {
467: return i; // Name found
468: } else if (test < 0) {
469: last = i - 1;
470: } else {
471: first = i + 1;
472: }
473: }
474:
475: if (first > i) {
476: i = first;
477: }
478: }
479:
480: return -1 - i; // not-found has to be encoded.
481:
482: } // findNamePoint(String):int
483:
484: /** This findNamePoint is for DOM Level 2 Namespaces.
485: */
486: protected int findNamePoint(String namespaceURI, String name) {
487:
488: if (nodes == null)
489: return -1;
490: if (name == null)
491: return -1;
492:
493: // This is a linear search through the same nodes Vector.
494: // The Vector is sorted on the DOM Level 1 nodename.
495: // The DOM Level 2 NS keys are namespaceURI and Localname,
496: // so we must linear search thru it.
497: // In addition, to get this to work with nodes without any namespace
498: // (namespaceURI and localNames are both null) we then use the nodeName
499: // as a seconday key.
500: for (int i = 0; i < nodes.size(); i++) {
501: NodeImpl a = (NodeImpl) nodes.elementAt(i);
502: String aNamespaceURI = a.getNamespaceURI();
503: String aLocalName = a.getLocalName();
504: if (namespaceURI == null) {
505: if (aNamespaceURI == null
506: && (name.equals(aLocalName) || (aLocalName == null && name
507: .equals(a.getNodeName()))))
508: return i;
509: } else {
510: if (namespaceURI.equals(aNamespaceURI)
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: for (int i = 0; i < nodes.size(); i++) {
524: Node n = (Node) nodes.elementAt(i);
525: if (n == a)
526: return true;
527: if (n == b)
528: return false;
529: }
530: }
531:
532: return false;
533: }
534:
535: /**
536: * NON-DOM: Remove attribute at specified index
537: */
538: protected void removeItem(int index) {
539: if (nodes != null && index < nodes.size()) {
540: nodes.removeElementAt(index);
541: }
542: }
543:
544: protected Object getItem(int index) {
545: if (nodes != null) {
546: return nodes.elementAt(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.setElementAt(arg, i);
555: } else {
556: // If we can't find by namespaceURI, localName, then we find by
557: // nodeName so we know where to insert.
558: i = findNamePoint(arg.getNodeName(), 0);
559: if (i >= 0) {
560: nodes.insertElementAt(arg, i);
561: } else {
562: i = -1 - i; // Insert point (may be end of list)
563: if (null == nodes) {
564: nodes = new Vector(5, 10);
565: }
566: nodes.insertElementAt(arg, i);
567: }
568: }
569: return i;
570: }
571:
572: /**
573: * NON-DOM: copy content of this map into the specified vector
574: *
575: * @param list Vector to copy information into.
576: * @return A copy of this node named map
577: */
578: protected Vector cloneMap(Vector list) {
579: if (list == null) {
580: list = new Vector(5, 10);
581: }
582: list.setSize(0);
583: if (nodes != null) {
584: for (int i = 0; i < nodes.size(); i++) {
585: list.insertElementAt(nodes.elementAt(i), i);
586: }
587: }
588:
589: return list;
590: }
591:
592: protected int getNamedItemIndex(String namespaceURI,
593: String localName) {
594: return findNamePoint(namespaceURI, localName);
595: }
596:
597: /**
598: * NON-DOM remove all elements from this map
599: */
600: public void removeAll() {
601: if (nodes != null) {
602: nodes.removeAllElements();
603: }
604: }
605:
606: } // class NamedNodeMapImpl
|