001: /**
002: * org/ozone-db/xml/dom/ElementImpl.java
003: *
004: * The contents of this file are subject to the OpenXML Public
005: * License Version 1.0; you may not use this file except in compliance
006: * with the License. You may obtain a copy of the License at
007: * http://www.openxml.org/license.html
008: *
009: * THIS SOFTWARE IS DISTRIBUTED ON AN "AS IS" BASIS WITHOUT WARRANTY
010: * OF ANY KIND, EITHER EXPRESSED OR IMPLIED. THE INITIAL DEVELOPER
011: * AND ALL CONTRIBUTORS SHALL NOT BE LIABLE FOR ANY DAMAGES AS A
012: * RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
013: * DERIVATIVES. SEE THE LICENSE FOR THE SPECIFIC LANGUAGE GOVERNING
014: * RIGHTS AND LIMITATIONS UNDER THE LICENSE.
015: *
016: * The Initial Developer of this code under the License is Assaf Arkin.
017: * Portions created by Assaf Arkin are Copyright (C) 1998, 1999.
018: * All Rights Reserved.
019: */
020:
021: /**
022: * Changes for Persistent DOM running with ozone are
023: * Copyright 1999 by SMB GmbH. All rights reserved.
024: */package org.ozoneDB.xml.dom;
025:
026: import java.util.*;
027: import java.io.*;
028: import org.w3c.dom.*;
029: import org.ozoneDB.*;
030:
031: /**
032: * The most common node type, {@link org.w3c.dom.Element} inherits the generic
033: * {@link Node} interface and adds support for retrieving and setting attributes
034: * either as nodes or as strings.
035: * <P>
036: * Notes:
037: * <OL>
038: * <LI>Node type is {@link org.w3c.dom.Node#ELEMENT_NODE}
039: * <LI>Node supports childern
040: * <LI>Node has no value
041: * <LI>Node has attributes
042: * </OL>
043: * <P>
044: * To speed up implementation, all attributes are implemented as double-linked
045: * list implemented using {@link NodeImpl#_parent}, {@link NodeImpl#_nextNode} and
046: * {@link NodeImpl#_prevNode}. This support is provided to through {@link
047: * #getNamedAttr}, {@link #appendAttr} and {@link #removeAttr} methods.
048: *
049: *
050: * @version $Revision: 1.1 $ $Date: 2001/12/18 11:03:24 $
051: * @author <a href="mailto:arkin@trendline.co.il">Assaf Arkin</a>
052: * @see org.w3c.dom.Element
053: * @see org.w3c.dom.Attr
054: * @see org.w3c.dom.NamedNodeMap
055: * @see AttrImpl
056: */
057: public class ElementImpl extends NodeImpl implements ElementProxy,
058: Externalizable {
059:
060: final static long serialVersionUID = 1;
061:
062: public Node getNamedItemNS(java.lang.String namespaceURI,
063: java.lang.String localName) {
064: throw new DOMExceptionImpl(
065: DOMException.NOT_SUPPORTED_ERR,
066: "Element.getNamedItemNS(): ozone's persistent DOM doesn't support DOM level 2 yet.");
067: }
068:
069: public Node setNamedItemNS(Node arg) throws DOMException {
070: throw new DOMExceptionImpl(
071: DOMException.NOT_SUPPORTED_ERR,
072: "Element.setNamedItemNS(): ozone's persistent DOM doesn't support DOM level 2 yet.");
073: }
074:
075: public Node removeNamedItemNS(java.lang.String namespaceURI,
076: java.lang.String localName) throws DOMException {
077: throw new DOMExceptionImpl(
078: DOMException.NOT_SUPPORTED_ERR,
079: "Element.removeNamedItemNS(): ozone's persistent DOM doesn't support DOM level 2 yet.");
080: }
081:
082: public Attr getAttributeNodeNS(java.lang.String namespaceURI,
083: java.lang.String localName) {
084: throw new DOMExceptionImpl(
085: DOMException.NOT_SUPPORTED_ERR,
086: "Element.getAttributeNodeNS(): ozone's persistent DOM doesn't support DOM level 2 yet.");
087: }
088:
089: public Attr setAttributeNodeNS(Attr newAttr) throws DOMException {
090: throw new DOMExceptionImpl(
091: DOMException.NOT_SUPPORTED_ERR,
092: "Element.setAttributeNodeNS(): ozone's persistent DOM doesn't support DOM level 2 yet.");
093: }
094:
095: public java.lang.String getAttributeNS(
096: java.lang.String namespaceURI, java.lang.String localName) {
097: throw new DOMExceptionImpl(
098: DOMException.NOT_SUPPORTED_ERR,
099: "Element.getAttributeNS(): ozone's persistent DOM doesn't support DOM level 2 yet.");
100: }
101:
102: public void setAttributeNS(java.lang.String namespaceURI,
103: java.lang.String qualifiedName, java.lang.String value)
104: throws DOMException {
105: throw new DOMExceptionImpl(
106: DOMException.NOT_SUPPORTED_ERR,
107: "Element.setAttributeNS(): ozone's persistent DOM doesn't support DOM level 2 yet.");
108: }
109:
110: public void removeAttributeNS(java.lang.String namespaceURI,
111: java.lang.String localName) throws DOMException {
112: throw new DOMExceptionImpl(
113: DOMException.NOT_SUPPORTED_ERR,
114: "Element.removeAttributeNS(): ozone's persistent DOM doesn't support DOM level 2 yet.");
115: }
116:
117: public NodeList getElementsByTagNameNS(
118: java.lang.String namespaceURI, java.lang.String localName) {
119: throw new DOMExceptionImpl(
120: DOMException.NOT_SUPPORTED_ERR,
121: "Element.getNamedItemNS(): ozone's persistent DOM doesn't support DOM level 2 yet.");
122: }
123:
124: public boolean hasAttributes() {
125: throw new DOMExceptionImpl(
126: DOMException.NOT_SUPPORTED_ERR,
127: "Element.hasAttributes(): ozone's persistent DOM doesn't support DOM level 2 yet.");
128: }
129:
130: public boolean hasAttribute(String name) {
131: throw new DOMExceptionImpl(
132: DOMException.NOT_SUPPORTED_ERR,
133: "Element.hasAttribute(): ozone's persistent DOM doesn't support DOM level 2 yet.");
134: }
135:
136: public boolean hasAttributeNS(String namespaceURI, String localname) {
137: throw new DOMExceptionImpl(
138: DOMException.NOT_SUPPORTED_ERR,
139: "Element.hasAttributeNS(): ozone's persistent DOM doesn't support DOM level 2 yet.");
140: }
141:
142: public final short getNodeType() {
143: return ELEMENT_NODE;
144: }
145:
146: public final Enumeration elements() {
147: return (Enumeration) null;
148: }
149:
150: public void setFirstAttr(Attr attribute) {
151: _firstAttr = (AttrProxy) attribute;
152: }
153:
154: public void setLastAttr(Attr attribute) {
155: _lastAttr = (AttrProxy) attribute;
156: }
157:
158: public void setAttrCount(int count) {
159: _attrCount = count;
160: }
161:
162: /**
163: * Returns the name of the tag, same as calling {@link #getNodeName}.
164: * In XML documents, the return value preserves case. In HTML documents,
165: * the return value is always upper case regardless of the original value.
166: *
167: * @return Tag name
168: */
169: public final String getTagName() {
170: return getNodeName();
171: }
172:
173: public final void setNodeValue(String value) {
174: throw new DOMExceptionImpl(DOMException.NO_DATA_ALLOWED_ERR,
175: "This node type does not support values.");
176: }
177:
178: /**
179: * Returns a list of elements extracted based on their tag name (or all of
180: * them if the tag name is "*"). The returned list is a snapshot of the
181: * element's contents at the time of calling. Subsequent updates to the
182: * element are not reflected in the list. This might result in inaccuracies
183: * when working from multiple threads.
184: *
185: * @param tagName The element tag name to look for or "*" for all elements
186: * @return A snapshot of the named elements contained within this element
187: */
188: public synchronized final NodeList getElementsByTagName(
189: String tagName) {
190: // The full implementation can be found in ElementListImpl
191: return (NodeList) new org.ozoneDB.xml.dom.ElementListImpl(this ,
192: tagName);
193: }
194:
195: public synchronized final void normalize() {
196: Node node;
197: Node next;
198:
199: // Run through all child nodes of this element. If a particular child
200: // is an Element, normalize it. If a particular child is a Text and is
201: // followed by a second Text node, concatenate the data from the second
202: // to the first and remove the second node.
203: node = getFirstChild();
204: while (node != null) {
205: if (node instanceof ElementProxy) {
206: ((ElementProxy) node).normalize();
207: } else {
208: if (node instanceof TextProxy) {
209: next = node.getNextSibling();
210: while (next != null && next instanceof TextProxy) {
211: ((TextProxy) node)
212: .appendData(((TextProxy) next)
213: .getData());
214: removeChild(next);
215: next = node.getNextSibling();
216: }
217: }
218: }
219: node = node.getNextSibling();
220: }
221: }
222:
223: public final NamedNodeMap getAttributes() {
224: // This is a NamedNodeMap of all its attributes.
225: // return this;
226: return (NamedNodeMap) self();
227: }
228:
229: public synchronized final String getAttribute(String name) {
230: AttrProxy attr;
231:
232: // Look for the named attribute and return it's value.
233: attr = (AttrProxy) getNamedAttr(name);
234: if (attr == null) {
235: return "";
236: }
237: return attr.getValue();
238: }
239:
240: public synchronized final void setAttribute(String name,
241: String value) throws DOMException {
242: AttrProxy attr;
243:
244: if (isReadOnly()) {
245: throw new DOMExceptionImpl(
246: DOMException.NO_MODIFICATION_ALLOWED_ERR);
247: }
248: // If attribute value is null, might as well remove attribute. This will
249: // either save space, or return the default value instead.
250: if (value == null) {
251: removeAttribute(name);
252: } else {
253: try {
254: // Get the named attribute and change it's value. If the attribute
255: // does not exist, create a new attribute by that name and add it.
256: // Call setValue() to assure correct behavior.
257: attr = (AttrProxy) getNamedAttr(name);
258: if (attr == null) {
259: attr = (AttrProxy) database().createObject(
260: AttrImpl.class.getName());
261: attr.init(_ownerDocument, name, "");
262: appendAttr(attr);
263: }
264: attr.setValue(value);
265: } catch (Exception except) {
266: throw new DOMExceptionImpl(DOMExceptionImpl.PDOM_ERR,
267: except.getMessage());
268: }
269: }
270: }
271:
272: public synchronized final void removeAttribute(String name) {
273: AttrProxy attr;
274:
275: attr = (AttrProxy) getNamedAttr(name);
276: if (attr != null) {
277: removeAttr(attr);
278: }
279: }
280:
281: public final Attr getAttributeNode(String name) {
282: return getNamedAttr(name);
283: }
284:
285: public synchronized final Attr setAttributeNode(Attr newAttr)
286: throws DOMException {
287: AttrProxy oldAttr;
288:
289: if (isReadOnly()) {
290: throw new DOMExceptionImpl(
291: DOMException.NO_MODIFICATION_ALLOWED_ERR);
292: }
293: if (newAttr == null || !(newAttr instanceof AttrProxy)) {
294: throw new DOMExceptionImpl(DOMException.WRONG_DOCUMENT_ERR);
295: }
296: // Note: getParentNode() will return null.
297: if (((AttrProxy) newAttr).getParentNode() != null) {
298: throw new DOMExceptionImpl(DOMException.INUSE_ATTRIBUTE_ERR);
299: }
300: synchronized (newAttr) {
301: oldAttr = (AttrProxy) getNamedAttr(newAttr.getName());
302: if (oldAttr != null) {
303: removeAttr(oldAttr);
304: }
305: appendAttr((AttrProxy) newAttr);
306: }
307: return oldAttr;
308: }
309:
310: public synchronized final Attr removeAttributeNode(Attr oldAttr) {
311: if (isReadOnly()) {
312: throw new DOMExceptionImpl(
313: DOMException.NO_MODIFICATION_ALLOWED_ERR);
314: }
315: if (!(oldAttr instanceof Attr)) {
316: throw new DOMExceptionImpl(
317: DOMException.HIERARCHY_REQUEST_ERR,
318: "Node is not an attribute compatible with this element.");
319: }
320: oldAttr = removeAttr((AttrProxy) oldAttr);
321: if (oldAttr == null) {
322: throw new DOMExceptionImpl(DOMException.NOT_FOUND_ERR);
323: }
324: return oldAttr;
325: }
326:
327: public synchronized final Node getNamedItem(String name) {
328: return getNamedAttr(name);
329: }
330:
331: public final Node setNamedItem(Node arg) throws DOMException {
332: if (!(arg instanceof AttrProxy)) {
333: throw new DOMExceptionImpl(
334: DOMException.HIERARCHY_REQUEST_ERR,
335: "Node is not an attribute compatible with this element.");
336: }
337: return setAttributeNode((AttrProxy) arg);
338: }
339:
340: public synchronized final Node removeNamedItem(String name)
341: throws DOMException {
342: AttrProxy attr;
343:
344: attr = (AttrProxy) getNamedAttr(name);
345: if (attr != null) {
346: removeAttr(attr);
347: return attr;
348: } else {
349: return null;
350: }
351: }
352:
353: public synchronized final Node item(int index) {
354: AttrProxy attr;
355:
356: attr = _firstAttr;
357: while (attr != null && index > 0) {
358: // Note: getNextSibling() will return null.
359: attr = (AttrProxy) attr.getNextSibling();
360: --index;
361: }
362: return attr;
363: }
364:
365: public int getLength() {
366: return _attrCount;
367: }
368:
369: public synchronized boolean equals(Object other) {
370: ElementProxy otherX;
371: AttrProxy attr;
372: boolean equal;
373:
374: // If both objects are the same, return true. If one is null, or they
375: // do not belong to the same class, return false. Equality is not
376: // tested across different DOMs or different ClassLoaders.
377: if (this == other) {
378: return true;
379: }
380: if (other == null || !(other instanceof Element)) {
381: return false;
382: }
383: if (!super .equals(other)) {
384: return false;
385: }
386: synchronized (other) {
387: otherX = (ElementProxy) other;
388: equal = _attrCount == otherX.getLength();
389: if (equal) {
390: // Test for attributes first, this is the faster test and
391: // can tell elements apart quite easily. Since attributes might
392: // be out of sequence, retrieve attributes by name not by sequence.
393: // This test is recursive to some degree.
394: attr = _firstAttr;
395: while (equal && attr != null) {
396: equal = otherX.getNamedAttr(attr.getNodeName()) != null
397: && otherX.getNamedAttr(attr.getNodeName())
398: .equals(attr);
399: // Note: getNextSibling() will return null.
400: attr = (AttrProxy) attr.getNextSibling();
401: }
402: }
403: }
404: return equal;
405: }
406:
407: public final Object clone() {
408: ElementProxy clone = null;
409: try {
410: clone = (ElementProxy) _ownerDocument
411: .createElement(getNodeName());
412: cloneInto(clone, true);
413: } catch (Exception except) {
414: throw new DOMExceptionImpl(DOMExceptionImpl.PDOM_ERR,
415: except.getMessage());
416: }
417: return clone;
418: }
419:
420: public final Node cloneNode(boolean deep) {
421: ElementProxy clone = null;
422: try {
423: clone = (ElementProxy) _ownerDocument
424: .createElement(getNodeName());
425: cloneInto(clone, deep);
426: } catch (Exception except) {
427: throw new DOMExceptionImpl(DOMExceptionImpl.PDOM_ERR,
428: except.getMessage());
429: }
430: return clone;
431: }
432:
433: public String toString() {
434: String name;
435:
436: name = getTagName();
437: if (name.length() > 32) {
438: name = name.substring(0, 32) + "..";
439: }
440: return "Element node: [" + name + "] (" + getChildCount()
441: + " nodes)";
442: }
443:
444: public synchronized void cloneInto(NodeProxy into, boolean deep) {
445: AttrProxy attr;
446: ElementProxy intoX;
447:
448: super .cloneInto(into, deep);
449:
450: intoX = (ElementProxy) into;
451: // Duplicate all attributes . Note that attributes are duplicated with deep
452: // cloning, since an attribute might contain Text and EntityReference nodes.
453: intoX.setFirstAttr(null);
454: intoX.setLastAttr(null);
455: intoX.setAttrCount(0);
456: attr = _firstAttr;
457: while (attr != null) {
458: intoX.appendAttr((AttrProxy) attr.cloneNode(true));
459: // Note: getNextSibling() will return null.
460: attr = (AttrProxy) attr.getNextSibling();
461: }
462: }
463:
464: protected boolean supportsChildern() {
465: return true;
466: }
467:
468: /**
469: * Returns the named attribute or null if attribute not found.
470: *
471: * @param name The name of the attribute to return
472: * @return The named attribute or null
473: */
474: public synchronized final Attr getNamedAttr(String name) {
475: AttrProxy attr;
476:
477: attr = _firstAttr;
478: while (attr != null) {
479: if (attr.getName().equals(name)) {
480: return attr;
481: }
482: // Note: getNextSibling() will return null.
483: attr = (AttrProxy) attr.getNextSibling();
484: }
485: return null;
486: }
487:
488: /**
489: * Append <TT>newAttr</TT> as the last attribute of this element.
490: * If <TT>newAttr</TT> is not an attribute of this DOM, or is already in use
491: * by some element, an exception is thrown.
492: *
493: * @param newAttr The new attribute to add
494: * @return The newly added attribute
495: * @throws org.w3c.dom.DOMException <TT>NO_MODIFICATION_ALLOWED_ERR</TT>
496: * Node is read-only and cannot be modified
497: * @throws org.w3c.dom.DOMException <TT>WRONG_DOCUMENT_ERR</TT>
498: * <TT>newAttr</TT> is not an attribute in this DOM
499: * @throws org.w3c.dom.DOMException <TT>INUSE_ATTRIBUTE_ERR</TT>
500: * <TT>newAttr</TT> is already in use by some other element
501: */
502: public synchronized final AttrProxy appendAttr(AttrProxy newAttr) {
503: // Make sure the node is not read-only and the attribute can be added to it.
504: if (isReadOnly()) {
505: throw new DOMExceptionImpl(
506: DOMException.NO_MODIFICATION_ALLOWED_ERR);
507: }
508: if (newAttr == null) {
509: throw new DOMExceptionImpl(DOMException.WRONG_DOCUMENT_ERR,
510: "Attribute does not belong to same document as this element.");
511: }
512: if (newAttr.getParentNode() == this ) {
513: return newAttr;
514: }
515: if (newAttr.getParentNode() != null) {
516: throw new DOMExceptionImpl(DOMException.INUSE_ATTRIBUTE_ERR);
517: }
518:
519: // We're going to mess with this attribute, so make sure no other thread
520: // is touching it
521: synchronized (newAttr) {
522: newAttr.setParentNode(this );
523: newAttr.setOwnerDocument(getOwnerDocument());
524:
525: // If the list has no end (it is empty) then newAttr is added as the
526: // only attribute in it.
527: if (_lastAttr == null) {
528: _lastAttr = newAttr;
529: _firstAttr = newAttr;
530: newAttr.setPreviousSibling(null);
531: newAttr.setNextSibling(null);
532: } else {
533: // newAttr becomes the new end of the list, adjusting the previous
534: // last attribute.
535: _lastAttr.setNextSibling(newAttr);
536: newAttr.setPreviousSibling(_lastAttr);
537: newAttr.setNextSibling(null);
538: _lastAttr = newAttr;
539: }
540: // Keep this count accurate at all times.
541: ++_attrCount;
542: }
543: return newAttr;
544: }
545:
546: /**
547: * Remove <TT>oldAttr</TT> from this element. If <TT>oldAttr</TT> is not an
548: * attribute of this element, an exception is thrown.
549: *
550: * @param oldAttr The attribute to remove
551: * @return The removed attribute
552: * @throws org.w3c.dom.DOMException <TT>NO_MODIFICATION_ALLOWED_ERR</TT>
553: * Node is read-only and cannot be modified
554: * @throws org.w3c.dom.DOMException <TT>NOT_FOUND_ERR</TT>
555: * <TT>oldAttr</TT> is not an attribute of this element
556: */
557: public synchronized final AttrProxy removeAttr(AttrProxy oldAttr)
558: throws DOMException {
559:
560: // Make sure the element is not read-only and the attribute belonds to it.
561: if (isReadOnly()) {
562: throw new DOMExceptionImpl(
563: DOMException.NO_MODIFICATION_ALLOWED_ERR);
564: }
565: // Note: getParentNode() will return null.
566: if (oldAttr == null
567: || !((OzoneProxy) oldAttr.getParentNode()).remoteID()
568: .equals(container().id())) {
569: throw new DOMExceptionImpl(DOMException.NOT_FOUND_ERR,
570: "Not an attribute of this element.");
571: }
572:
573: // We're going to mess with this attribute node, so make sure no other
574: // thread is touching it
575: synchronized (oldAttr) {
576: // Attribute becomes orphan. It is no longer first or last attribute of
577: // this element. Removed from linked list.
578: ((NodeProxy) oldAttr).setParentNode(null);
579: if (_firstAttr == oldAttr) {
580: _firstAttr = (AttrProxy) oldAttr.getNextSibling();
581: }
582: if (_lastAttr == oldAttr) {
583: _lastAttr = (AttrProxy) oldAttr.getPreviousSibling();
584: }
585: if (oldAttr.getPreviousSibling() != null) {
586: ((NodeProxy) oldAttr.getPreviousSibling())
587: .setNextSibling(oldAttr.getNextSibling());
588: }
589: if (oldAttr.getNextSibling() != null) {
590: ((NodeProxy) oldAttr.getNextSibling())
591: .setPreviousSibling(oldAttr
592: .getPreviousSibling());
593: }
594: ((NodeProxy) oldAttr).setPreviousSibling(null);
595: ((NodeProxy) oldAttr).setNextSibling(null);
596: // Keep this count accurate at all times.
597: --_attrCount;
598: }
599: return oldAttr;
600: }
601:
602: /**
603: * Constructor requires only owner document and tag name of element.
604: * Tag name is case sensitive for XML, but converted to upper case for
605: * HTML documents.
606: *
607: * @param owner Owner document of this element
608: * @param name The tag name of the element
609: */
610: public ElementImpl(DocumentImpl owner, String name) {
611: super (owner, name, null, true);
612: // Make sure that all element tag names are converted to upper case
613: // for some documents (HTML).
614: }
615:
616: public ElementImpl() {
617: super ();
618: }
619:
620: public void init(NodeProxy owner, Dictionary dictionary) {
621: // System.out.println ("ElementImpl.init()");
622: }
623:
624: public void onDelete() throws Exception {
625: // remove all Attributes of this Element-Node
626: deleteAllChildern(_firstAttr);
627: }
628:
629: private void deleteAllChildern(Node node) throws Exception {
630: Node dummyNode;
631: while (node != null) {
632: if (node.hasChildNodes()) {
633: deleteAllChildern(node.getFirstChild());
634: }
635: dummyNode = node.getNextSibling();
636: database().deleteObject((OzoneRemote) node);
637: node = dummyNode;
638: }
639: }
640:
641: /** */
642: public void writeExternal(ObjectOutput out) throws IOException {
643: super .writeExternal(out);
644: out.writeByte((byte) _attrCount);
645: if (_attrCount > 0) {
646: out.writeObject(_firstAttr);
647: out.writeObject(_lastAttr);
648: }
649: }
650:
651: /** */
652: public void readExternal(ObjectInput in) throws IOException,
653: ClassNotFoundException {
654: super .readExternal(in);
655: _attrCount = in.readByte();
656: if (_attrCount > 0) {
657: _firstAttr = (AttrProxy) in.readObject();
658: _lastAttr = (AttrProxy) in.readObject();
659: } else {
660: _firstAttr = null;
661: _lastAttr = null;
662: }
663: }
664:
665: /**
666: * The attributes of this element are arranged in a doubly linked lists.
667: * This reference identifies the first attribute in the list.
668: */
669: private AttrProxy _firstAttr;
670:
671: /**
672: * The attributes of this element are arranged in a doubly linked lists.
673: * This reference identifies the last attribute in the list.
674: */
675: private AttrProxy _lastAttr;
676:
677: /**
678: * Counts how many attributes belong to this element.
679: */
680: private int _attrCount;
681:
682: }
|