001: /*--
002:
003: $Id: Document.java,v 1.1 2005/04/27 09:32:39 wittek Exp $
004:
005: Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
006: All rights 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 disclaimer that follows
017: these conditions in the documentation and/or other materials
018: provided with the distribution.
019:
020: 3. The name "JDOM" must not be used to endorse or promote products
021: derived from this software without prior written permission. For
022: written permission, please contact <request_AT_jdom_DOT_org>.
023:
024: 4. Products derived from this software may not be called "JDOM", nor
025: may "JDOM" appear in their name, without prior written permission
026: from the JDOM Project Management <request_AT_jdom_DOT_org>.
027:
028: In addition, we request (but do not require) that you include in the
029: end-user documentation provided with the redistribution and/or in the
030: software itself an acknowledgement equivalent to the following:
031: "This product includes software developed by the
032: JDOM Project (http://www.jdom.org/)."
033: Alternatively, the acknowledgment may be graphical using the logos
034: available at http://www.jdom.org/images/logos.
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 JDOM AUTHORS OR THE PROJECT
040: 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: This software consists of voluntary contributions made by many
050: individuals on behalf of the JDOM Project and was originally
051: created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
052: Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
053: on the JDOM Project, please see <http://www.jdom.org/>.
054:
055: */
056:
057: package org.jdom;
058:
059: import java.util.*;
060: import org.jdom.filter.*;
061:
062: /**
063: * An XML document. Methods allow access to the root element as well as the
064: * {@link DocType} and other document-level information.
065: *
066: * @version $Revision: 1.1 $, $Date: 2005/04/27 09:32:39 $
067: * @author Brett McLaughlin
068: * @author Jason Hunter
069: * @author Jools Enticknap
070: * @author Bradley S. Huffman
071: */
072: public class Document implements Parent {
073:
074: private static final String CVS_ID = "@(#) $RCSfile: Document.java,v $ $Revision: 1.1 $ $Date: 2005/04/27 09:32:39 $ $Name: $";
075:
076: /**
077: * This document's content including comments, PIs, a possible
078: * DocType, and a root element.
079: * Subclassers have to track content using their own
080: * mechanism.
081: */
082: ContentList content = new ContentList(this );
083:
084: /**
085: * See http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/core.html#baseURIs-Considerations
086: */
087: protected String baseURI = null;
088:
089: // Supports the setProperty/getProperty calls
090: private HashMap propertyMap = null;
091:
092: /**
093: * Creates a new empty document. A document must have a root element,
094: * so this document will not be well-formed and accessor methods will
095: * throw an IllegalStateException if this document is accessed before a
096: * root element is added. This method is most useful for build tools.
097: */
098: public Document() {
099: }
100:
101: /**
102: * This will create a new <code>Document</code>,
103: * with the supplied <code>{@link Element}</code>
104: * as the root element, the supplied
105: * <code>{@link DocType}</code> declaration, and the specified
106: * base URI.
107: *
108: * @param rootElement <code>Element</code> for document root.
109: * @param docType <code>DocType</code> declaration.
110: * @param baseURI the URI from which this doucment was loaded.
111: * @throws IllegalAddException if the given docType object
112: * is already attached to a document or the given
113: * rootElement already has a parent
114: */
115: public Document(Element rootElement, DocType docType, String baseURI) {
116: if (rootElement != null) {
117: setRootElement(rootElement);
118: }
119: if (docType != null) {
120: setDocType(docType);
121: }
122: if (baseURI != null) {
123: setBaseURI(baseURI);
124: }
125: }
126:
127: /**
128: * This will create a new <code>Document</code>,
129: * with the supplied <code>{@link Element}</code>
130: * as the root element and the supplied
131: * <code>{@link DocType}</code> declaration.
132: *
133: * @param rootElement <code>Element</code> for document root.
134: * @param docType <code>DocType</code> declaration.
135: * @throws IllegalAddException if the given DocType object
136: * is already attached to a document or the given
137: * rootElement already has a parent
138: */
139: public Document(Element rootElement, DocType docType) {
140: this (rootElement, docType, null);
141: }
142:
143: /**
144: * This will create a new <code>Document</code>,
145: * with the supplied <code>{@link Element}</code>
146: * as the root element, and no <code>{@link DocType}</code>
147: * declaration.
148: *
149: * @param rootElement <code>Element</code> for document root
150: * @throws IllegalAddException if the given rootElement already has
151: * a parent.
152: */
153: public Document(Element rootElement) {
154: this (rootElement, null, null);
155: }
156:
157: /**
158: * This will create a new <code>Document</code>,
159: * with the supplied list of content, and a
160: * <code>{@link DocType}</code> declaration only if the content
161: * contains a DocType instance. A null list is treated the
162: * same as the no-arg constructor.
163: *
164: * @param content <code>List</code> of starter content
165: * @throws IllegalAddException if the List contains more than
166: * one Element or objects of illegal types.
167: */
168: public Document(List content) {
169: setContent(content);
170: }
171:
172: public int getContentSize() {
173: return content.size();
174: }
175:
176: public int indexOf(Content child) {
177: return content.indexOf(child);
178: }
179:
180: // /**
181: // * Starting at the given index (inclusive), return the index of
182: // * the first child matching the supplied filter, or -1
183: // * if none is found.
184: // *
185: // * @return index of child, or -1 if none found.
186: // */
187: // private int indexOf(int start, Filter filter) {
188: // int size = getContentSize();
189: // for (int i = start; i < size; i++) {
190: // if (filter.matches(getContent(i))) {
191: // return i;
192: // }
193: // }
194: // return -1;
195: // }
196:
197: /**
198: * This will return <code>true</code> if this document has a
199: * root element, <code>false</code> otherwise.
200: *
201: * @return <code>true</code> if this document has a root element,
202: * <code>false</code> otherwise.
203: */
204: public boolean hasRootElement() {
205: return (content.indexOfFirstElement() < 0) ? false : true;
206: }
207:
208: /**
209: * This will return the root <code>Element</code>
210: * for this <code>Document</code>
211: *
212: * @return <code>Element</code> - the document's root element
213: * @throws IllegalStateException if the root element hasn't been set
214: */
215: public Element getRootElement() {
216: int index = content.indexOfFirstElement();
217: if (index < 0) {
218: throw new IllegalStateException("Root element not set");
219: }
220: return (Element) content.get(index);
221: }
222:
223: /**
224: * This sets the root <code>{@link Element}</code> for the
225: * <code>Document</code>. If the document already has a root
226: * element, it is replaced.
227: *
228: * @param rootElement <code>Element</code> to be new root.
229: * @return <code>Document</code> - modified Document.
230: * @throws IllegalAddException if the given rootElement already has
231: * a parent.
232: */
233: public Document setRootElement(Element rootElement) {
234: int index = content.indexOfFirstElement();
235: if (index < 0) {
236: content.add(rootElement);
237: } else {
238: content.set(index, rootElement);
239: }
240: return this ;
241: }
242:
243: /**
244: * Detach the root <code>{@link Element}</code> from this document.
245: *
246: * @return removed root <code>Element</code>
247: */
248: public Element detachRootElement() {
249: int index = content.indexOfFirstElement();
250: if (index < 0)
251: return null;
252: return (Element) removeContent(index);
253: }
254:
255: /**
256: * This will return the <code>{@link DocType}</code>
257: * declaration for this <code>Document</code>, or
258: * <code>null</code> if none exists.
259: *
260: * @return <code>DocType</code> - the DOCTYPE declaration.
261: */
262: public DocType getDocType() {
263: int index = content.indexOfDocType();
264: if (index < 0) {
265: return null;
266: } else {
267: return (DocType) content.get(index);
268: }
269: }
270:
271: /**
272: * This will set the <code>{@link DocType}</code>
273: * declaration for this <code>Document</code>. Note
274: * that a DocType can only be attached to one Document.
275: * Attempting to set the DocType to a DocType object
276: * that already belongs to a Document will result in an
277: * IllegalAddException being thrown.
278: *
279: * @param docType <code>DocType</code> declaration.
280: * @return object on which the method was invoked
281: * @throws IllegalAddException if the given docType is
282: * already attached to a Document.
283: */
284: public Document setDocType(DocType docType) {
285: if (docType == null) {
286: // Remove any existing doctype
287: int docTypeIndex = content.indexOfDocType();
288: if (docTypeIndex >= 0)
289: content.remove(docTypeIndex);
290: return this ;
291: }
292:
293: if (docType.getParent() != null) {
294: throw new IllegalAddException(docType,
295: "The DocType already is attached to a document");
296: }
297:
298: // Add DocType to head if new, replace old otherwise
299: int docTypeIndex = content.indexOfDocType();
300: if (docTypeIndex < 0) {
301: content.add(0, docType);
302: } else {
303: content.set(docTypeIndex, docType);
304: }
305:
306: return this ;
307: }
308:
309: /**
310: * Appends the child to the end of the content list.
311: *
312: * @param child child to append to end of content list
313: * @return the document on which the method was called
314: * @throws IllegalAddException if the given child already has a parent.
315: */
316: public Document addContent(Content child) {
317: content.add(child);
318: return this ;
319: }
320:
321: /**
322: * Appends all children in the given collection to the end of
323: * the content list. In event of an exception during add the
324: * original content will be unchanged and the objects in the supplied
325: * collection will be unaltered.
326: *
327: * @param c collection to append
328: * @return the document on which the method was called
329: * @throws IllegalAddException if any item in the collection
330: * already has a parent or is of an illegal type.
331: */
332: public Document addContent(Collection c) {
333: content.addAll(c);
334: return this ;
335: }
336:
337: /**
338: * Inserts the child into the content list at the given index.
339: *
340: * @param index location for adding the collection
341: * @param child child to insert
342: * @return the parent on which the method was called
343: * @throws IndexOutOfBoundsException if index is negative or beyond
344: * the current number of children
345: * @throws IllegalAddException if the given child already has a parent.
346: */
347: public Document addContent(int index, Content child) {
348: content.add(index, child);
349: return this ;
350: }
351:
352: /**
353: * Inserts the content in a collection into the content list
354: * at the given index. In event of an exception the original content
355: * will be unchanged and the objects in the supplied collection will be
356: * unaltered.
357: *
358: * @param index location for adding the collection
359: * @param c collection to insert
360: * @return the parent on which the method was called
361: * @throws IndexOutOfBoundsException if index is negative or beyond
362: * the current number of children
363: * @throws IllegalAddException if any item in the collection
364: * already has a parent or is of an illegal type.
365: */
366: public Document addContent(int index, Collection c) {
367: content.addAll(index, c);
368: return this ;
369: }
370:
371: public List cloneContent() {
372: int size = getContentSize();
373: List list = new ArrayList(size);
374: for (int i = 0; i < size; i++) {
375: Content child = getContent(i);
376: list.add(child.clone());
377: }
378: return list;
379: }
380:
381: public Content getContent(int index) {
382: return (Content) content.get(index);
383: }
384:
385: // public Content getChild(Filter filter) {
386: // int i = indexOf(0, filter);
387: // return (i < 0) ? null : getContent(i);
388: // }
389:
390: /**
391: * This will return all content for the <code>Document</code>.
392: * The returned list is "live" in document order and changes to it
393: * affect the document's actual content.
394: *
395: * <p>
396: * Sequential traversal through the List is best done with a Iterator
397: * since the underlying implement of List.size() may require walking the
398: * entire list.
399: * </p>
400: *
401: * @return <code>List</code> - all Document content
402: * @throws IllegalStateException if the root element hasn't been set
403: */
404: public List getContent() {
405: if (!hasRootElement())
406: throw new IllegalStateException("Root element not set");
407: return content;
408: }
409:
410: /**
411: * Return a filtered view of this <code>Document</code>'s content.
412: *
413: * <p>
414: * Sequential traversal through the List is best done with a Iterator
415: * since the underlying implement of List.size() may require walking the
416: * entire list.
417: * </p>
418: *
419: * @param filter <code>Filter</code> to apply
420: * @return <code>List</code> - filtered Document content
421: * @throws IllegalStateException if the root element hasn't been set
422: */
423: public List getContent(Filter filter) {
424: if (!hasRootElement())
425: throw new IllegalStateException("Root element not set");
426: return content.getView(filter);
427: }
428:
429: /**
430: * Removes all child content from this parent.
431: *
432: * @return list of the old children detached from this parent
433: */
434: public List removeContent() {
435: List old = new ArrayList(content);
436: content.clear();
437: return old;
438: }
439:
440: /**
441: * Remove all child content from this parent matching the supplied filter.
442: *
443: * @param filter filter to select which content to remove
444: * @return list of the old children detached from this parent
445: */
446: public List removeContent(Filter filter) {
447: List old = new ArrayList();
448: Iterator itr = content.getView(filter).iterator();
449: while (itr.hasNext()) {
450: Content child = (Content) itr.next();
451: old.add(child);
452: itr.remove();
453: }
454: return old;
455: }
456:
457: /**
458: * This sets the content of the <code>Document</code>. The supplied
459: * List should contain only objects of type <code>Element</code>,
460: * <code>Comment</code>, and <code>ProcessingInstruction</code>.
461: *
462: * <p>
463: * When all objects in the supplied List are legal and before the new
464: * content is added, all objects in the old content will have their
465: * parentage set to null (no parent) and the old content list will be
466: * cleared. This has the effect that any active list (previously obtained
467: * with a call to {@link #getContent}) will also
468: * change to reflect the new content. In addition, all objects in the
469: * supplied List will have their parentage set to this document, but the
470: * List itself will not be "live" and further removals and additions will
471: * have no effect on this document content. If the user wants to continue
472: * working with a "live" list, then a call to setContent should be
473: * followed by a call to {@link #getContent} to
474: * obtain a "live" version of the content.
475: * </p>
476: *
477: * <p>
478: * Passing a null or empty List clears the existing content.
479: * </p>
480: *
481: * <p>
482: * In event of an exception the original content will be unchanged and
483: * the objects in the supplied content will be unaltered.
484: * </p>
485: *
486: * @param newContent <code>List</code> of content to set
487: * @return this document modified
488: * @throws IllegalAddException if the List contains objects of
489: * illegal types or with existing parentage.
490: */
491: public Document setContent(Collection newContent) {
492: content.clearAndSet(newContent);
493: return this ;
494: }
495:
496: /**
497: *
498: * <p>
499: * Sets the effective URI from which this document was loaded,
500: * and against which relative URLs in this document will be resolved.
501: * </p>
502: *
503: * @param uri the base URI of this document
504: */
505: public final void setBaseURI(String uri) {
506: this .baseURI = uri; // XXX We don't check the URI
507: }
508:
509: /**
510: * <p>
511: * Returns the URI from which this document was loaded,
512: * or null if this is not known.
513: * </p>
514: *
515: * @return the base URI of this document
516: */
517: public final String getBaseURI() {
518: return baseURI;
519: }
520:
521: /*
522: * Replace the current child the given index with the supplied child.
523: * <p>
524: * In event of an exception the original content will be unchanged and
525: * the supplied child will be unaltered.
526: * </p>
527: *
528: * @param index - index of child to replace.
529: * @param child - child to add.
530: * @throws IllegalAddException if the supplied child is already attached
531: * or not legal content for this parent.
532: * @throws IndexOutOfBoundsException if index is negative or greater
533: * than the current number of children.
534: */
535: public Document setContent(int index, Content child) {
536: content.set(index, child);
537: return this ;
538: }
539:
540: /**
541: * Replace the child at the given index whith the supplied
542: * collection.
543: * <p>
544: * In event of an exception the original content will be unchanged and
545: * the content in the supplied collection will be unaltered.
546: * </p>
547: *
548: * @param index - index of child to replace.
549: * @param collection - collection of content to add.
550: * @return object on which the method was invoked
551: * @throws IllegalAddException if the collection contains objects of
552: * illegal types.
553: * @throws IndexOutOfBoundsException if index is negative or greater
554: * than the current number of children.
555: */
556: public Document setContent(int index, Collection collection) {
557: content.remove(index);
558: content.addAll(index, collection);
559: return this ;
560: }
561:
562: public boolean removeContent(Content child) {
563: return content.remove(child);
564: }
565:
566: public Content removeContent(int index) {
567: return (Content) content.remove(index);
568: }
569:
570: /**
571: * Set this document's content to be the supplied child.
572: * <p>
573: * If the supplied child is legal content for a Document and before
574: * it is added, all content in the current content list will
575: * be cleared and all current children will have their parentage set to
576: * null.
577: * <p>
578: * This has the effect that any active list (previously obtained with
579: * a call to one of the {@link #getContent} methods will also change
580: * to reflect the new content. In addition, all content in the supplied
581: * collection will have their parentage set to this Document. If the user
582: * wants to continue working with a <b>"live"</b> list of this Document's
583: * child, then a call to setContent should be followed by a call to one
584: * of the {@link #getContent} methods to obtain a <b>"live"</b>
585: * version of the children.
586: * <p>
587: * Passing a null child clears the existing content.
588: * <p>
589: * In event of an exception the original content will be unchanged and
590: * the supplied child will be unaltered.
591: *
592: * @param child new content to replace existing content
593: * @return the parent on which the method was called
594: * @throws IllegalAddException if the supplied child is already attached
595: * or not legal content for this parent
596: */
597: public Document setContent(Content child) {
598: content.clear();
599: content.add(child);
600: return this ;
601: }
602:
603: /**
604: * This returns a <code>String</code> representation of the
605: * <code>Document</code>, suitable for debugging. If the XML
606: * representation of the <code>Document</code> is desired,
607: * {@link org.jdom.output.XMLOutputter#outputString(Document)}
608: * should be used.
609: *
610: * @return <code>String</code> - information about the
611: * <code>Document</code>
612: */
613: public String toString() {
614: StringBuffer stringForm = new StringBuffer()
615: .append("[Document: ");
616:
617: DocType docType = getDocType();
618: if (docType != null) {
619: stringForm.append(docType.toString()).append(", ");
620: } else {
621: stringForm.append(" No DOCTYPE declaration, ");
622: }
623:
624: Element rootElement = getRootElement();
625: if (rootElement != null) {
626: stringForm.append("Root is ")
627: .append(rootElement.toString());
628: } else {
629: stringForm.append(" No root element"); // shouldn't happen
630: }
631:
632: stringForm.append("]");
633:
634: return stringForm.toString();
635: }
636:
637: /**
638: * This tests for equality of this <code>Document</code> to the supplied
639: * <code>Object</code>.
640: *
641: * @param ob <code>Object</code> to compare to
642: * @return <code>boolean</code> whether the <code>Document</code> is
643: * equal to the supplied <code>Object</code>
644: */
645: public final boolean equals(Object ob) {
646: return (ob == this );
647: }
648:
649: /**
650: * This returns the hash code for this <code>Document</code>.
651: *
652: * @return <code>int</code> hash code
653: */
654: public final int hashCode() {
655: return super .hashCode();
656: }
657:
658: /**
659: * This will return a deep clone of this <code>Document</code>.
660: *
661: * @return <code>Object</code> clone of this <code>Document</code>
662: */
663: public Object clone() {
664: Document doc = null;
665:
666: try {
667: doc = (Document) super .clone();
668: } catch (CloneNotSupportedException ce) {
669: // Can't happen
670: }
671:
672: // The clone has a reference to this object's content list, so
673: // owerwrite with a empty list
674: doc.content = new ContentList(doc);
675:
676: // Add the cloned content to clone
677:
678: for (int i = 0; i < content.size(); i++) {
679: Object obj = content.get(i);
680: if (obj instanceof Element) {
681: Element element = (Element) ((Element) obj).clone();
682: doc.content.add(element);
683: } else if (obj instanceof Comment) {
684: Comment comment = (Comment) ((Comment) obj).clone();
685: doc.content.add(comment);
686: } else if (obj instanceof ProcessingInstruction) {
687: ProcessingInstruction pi = (ProcessingInstruction) ((ProcessingInstruction) obj)
688: .clone();
689: doc.content.add(pi);
690: } else if (obj instanceof DocType) {
691: DocType dt = (DocType) ((DocType) obj).clone();
692: doc.content.add(dt);
693: }
694: }
695:
696: return doc;
697: }
698:
699: /**
700: * Returns an iterator that walks over all descendants in document order.
701: *
702: * @return an iterator to walk descendants
703: */
704: public Iterator getDescendants() {
705: return new DescendantIterator(this );
706: }
707:
708: /**
709: * Returns an iterator that walks over all descendants in document order
710: * applying the Filter to return only elements that match the filter rule.
711: * With filters you can match only Elements, only Comments, Elements or
712: * Comments, only Elements with a given name and/or prefix, and so on.
713: *
714: * @param filter filter to select which descendants to see
715: * @return an iterator to walk descendants within a filter
716: */
717: public Iterator getDescendants(Filter filter) {
718: return new FilterIterator(new DescendantIterator(this ), filter);
719: }
720:
721: public Parent getParent() {
722: return null; // documents never have parents
723: }
724:
725: /**
726: * @see org.jdom.Parent#getDocument()
727: */
728: public Document getDocument() {
729: return this ;
730: }
731:
732: /**
733: * Assigns an arbitrary object to be associated with this document under
734: * the given "id" string. Null values are permitted. Strings beginning
735: * with "http://www.jdom.org/ are reserved for JDOM use.
736: *
737: * @param id the id of the stored object
738: * @param value the object to store
739: */
740: public void setProperty(String id, Object value) {
741: if (propertyMap == null) {
742: propertyMap = new HashMap();
743: }
744: propertyMap.put(id, value);
745: }
746:
747: /**
748: * Returns the object associated with this document under the given "id"
749: * string, or null if there is no binding or if the binding explicitly
750: * stored a null value.
751: *
752: * @param id the id of the stored object to return
753: * @return the object associated with the given id
754: */
755: public Object getProperty(String id) {
756: if (propertyMap == null)
757: return null;
758: return propertyMap.get(id);
759: }
760: }
|