KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jdom > Document


1 /*--
2
3  $Id: Document.java,v 1.84 2004/08/31 21:47:51 jhunter Exp $
4
5  Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
6  All rights reserved.
7
8  Redistribution and use in source and binary forms, with or without
9  modification, are permitted provided that the following conditions
10  are met:
11
12  1. Redistributions of source code must retain the above copyright
13     notice, this list of conditions, and the following disclaimer.
14
15  2. Redistributions in binary form must reproduce the above copyright
16     notice, this list of conditions, and the disclaimer that follows
17     these conditions in the documentation and/or other materials
18     provided with the distribution.
19
20  3. The name "JDOM" must not be used to endorse or promote products
21     derived from this software without prior written permission. For
22     written permission, please contact <request_AT_jdom_DOT_org>.
23
24  4. Products derived from this software may not be called "JDOM", nor
25     may "JDOM" appear in their name, without prior written permission
26     from the JDOM Project Management <request_AT_jdom_DOT_org>.
27
28  In addition, we request (but do not require) that you include in the
29  end-user documentation provided with the redistribution and/or in the
30  software itself an acknowledgement equivalent to the following:
31      "This product includes software developed by the
32       JDOM Project (http://www.jdom.org/)."
33  Alternatively, the acknowledgment may be graphical using the logos
34  available at http://www.jdom.org/images/logos.
35
36  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39  DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
40  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47  SUCH DAMAGE.
48
49  This software consists of voluntary contributions made by many
50  individuals on behalf of the JDOM Project and was originally
51  created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
52  Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
53  on the JDOM Project, please see <http://www.jdom.org/>.
54
55  */

56
57 package org.jdom;
58
59 import java.util.*;
60 import org.jdom.filter.*;
61
62 /**
63  * An XML document. Methods allow access to the root element as well as the
64  * {@link DocType} and other document-level information.
65  *
66  * @version $Revision: 1.84 $, $Date: 2004/08/31 21:47:51 $
67  * @author Brett McLaughlin
68  * @author Jason Hunter
69  * @author Jools Enticknap
70  * @author Bradley S. Huffman
71  */

72 public class Document implements Parent {
73
74     private static final String JavaDoc CVS_ID =
75       "@(#) $RCSfile: Document.java,v $ $Revision: 1.84 $ $Date: 2004/08/31 21:47:51 $ $Name: $";
76
77     /**
78      * This document's content including comments, PIs, a possible
79      * DocType, and a root element.
80      * Subclassers have to track content using their own
81      * mechanism.
82      */

83     ContentList content = new ContentList(this);
84
85     /**
86      * See http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/core.html#baseURIs-Considerations
87      */

88     protected String JavaDoc baseURI = null;
89
90     // Supports the setProperty/getProperty calls
91
private HashMap propertyMap = null;
92
93     /**
94      * Creates a new empty document. A document must have a root element,
95      * so this document will not be well-formed and accessor methods will
96      * throw an IllegalStateException if this document is accessed before a
97      * root element is added. This method is most useful for build tools.
98      */

99     public Document() {}
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 JavaDoc 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 JavaDoc("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         }
238         else {
239             content.set(index, rootElement);
240         }
241         return this;
242     }
243
244     /**
245      * Detach the root <code>{@link Element}</code> from this document.
246      *
247      * @return removed root <code>Element</code>
248      */

249     public Element detachRootElement() {
250         int index = content.indexOfFirstElement();
251         if (index < 0)
252             return null;
253         return (Element) removeContent(index);
254     }
255
256     /**
257      * This will return the <code>{@link DocType}</code>
258      * declaration for this <code>Document</code>, or
259      * <code>null</code> if none exists.
260      *
261      * @return <code>DocType</code> - the DOCTYPE declaration.
262      */

263     public DocType getDocType() {
264         int index = content.indexOfDocType();
265         if (index < 0) {
266             return null;
267         }
268         else {
269             return (DocType) content.get(index);
270         }
271     }
272
273     /**
274      * This will set the <code>{@link DocType}</code>
275      * declaration for this <code>Document</code>. Note
276      * that a DocType can only be attached to one Document.
277      * Attempting to set the DocType to a DocType object
278      * that already belongs to a Document will result in an
279      * IllegalAddException being thrown.
280      *
281      * @param docType <code>DocType</code> declaration.
282      * @return object on which the method was invoked
283      * @throws IllegalAddException if the given docType is
284      * already attached to a Document.
285      */

286     public Document setDocType(DocType docType) {
287         if (docType == null) {
288             // Remove any existing doctype
289
int docTypeIndex = content.indexOfDocType();
290             if (docTypeIndex >= 0) content.remove(docTypeIndex);
291             return this;
292         }
293
294         if (docType.getParent() != null) {
295             throw new IllegalAddException(docType,
296                               "The DocType already is attached to a document");
297         }
298
299         // Add DocType to head if new, replace old otherwise
300
int docTypeIndex = content.indexOfDocType();
301         if (docTypeIndex < 0) {
302             content.add(0, docType);
303         }
304         else {
305             content.set(docTypeIndex, docType);
306         }
307
308         return this;
309     }
310
311     /**
312      * Appends the child to the end of the content list.
313      *
314      * @param child child to append to end of content list
315      * @return the document on which the method was called
316      * @throws IllegalAddException if the given child already has a parent.
317      */

318     public Document addContent(Content child) {
319         content.add(child);
320         return this;
321     }
322
323     /**
324      * Appends all children in the given collection to the end of
325      * the content list. In event of an exception during add the
326      * original content will be unchanged and the objects in the supplied
327      * collection will be unaltered.
328      *
329      * @param c collection to append
330      * @return the document on which the method was called
331      * @throws IllegalAddException if any item in the collection
332      * already has a parent or is of an illegal type.
333      */

334     public Document addContent(Collection c) {
335         content.addAll(c);
336         return this;
337     }
338
339     /**
340      * Inserts the child into the content list at the given index.
341      *
342      * @param index location for adding the collection
343      * @param child child to insert
344      * @return the parent on which the method was called
345      * @throws IndexOutOfBoundsException if index is negative or beyond
346      * the current number of children
347      * @throws IllegalAddException if the given child already has a parent.
348      */

349     public Document addContent(int index, Content child) {
350         content.add(index, child);
351         return this;
352     }
353
354     /**
355      * Inserts the content in a collection into the content list
356      * at the given index. In event of an exception the original content
357      * will be unchanged and the objects in the supplied collection will be
358      * unaltered.
359      *
360      * @param index location for adding the collection
361      * @param c collection to insert
362      * @return the parent on which the method was called
363      * @throws IndexOutOfBoundsException if index is negative or beyond
364      * the current number of children
365      * @throws IllegalAddException if any item in the collection
366      * already has a parent or is of an illegal type.
367      */

368     public Document addContent(int index, Collection c) {
369         content.addAll(index, c);
370         return this;
371     }
372
373     public List cloneContent() {
374         int size = getContentSize();
375         List list = new ArrayList(size);
376         for (int i = 0; i < size; i++) {
377             Content child = getContent(i);
378             list.add(child.clone());
379         }
380         return list;
381     }
382
383     public Content getContent(int index) {
384         return (Content) content.get(index);
385     }
386
387 // public Content getChild(Filter filter) {
388
// int i = indexOf(0, filter);
389
// return (i < 0) ? null : getContent(i);
390
// }
391

392     /**
393      * This will return all content for the <code>Document</code>.
394      * The returned list is "live" in document order and changes to it
395      * affect the document's actual content.
396      *
397      * <p>
398      * Sequential traversal through the List is best done with a Iterator
399      * since the underlying implement of List.size() may require walking the
400      * entire list.
401      * </p>
402      *
403      * @return <code>List</code> - all Document content
404      * @throws IllegalStateException if the root element hasn't been set
405      */

406     public List getContent() {
407         if (!hasRootElement())
408             throw new IllegalStateException JavaDoc("Root element not set");
409         return content;
410     }
411
412     /**
413      * Return a filtered view of this <code>Document</code>'s content.
414      *
415      * <p>
416      * Sequential traversal through the List is best done with a Iterator
417      * since the underlying implement of List.size() may require walking the
418      * entire list.
419      * </p>
420      *
421      * @param filter <code>Filter</code> to apply
422      * @return <code>List</code> - filtered Document content
423      * @throws IllegalStateException if the root element hasn't been set
424      */

425     public List getContent(Filter filter) {
426         if (!hasRootElement())
427             throw new IllegalStateException JavaDoc("Root element not set");
428         return content.getView(filter);
429     }
430
431     /**
432      * Removes all child content from this parent.
433      *
434      * @return list of the old children detached from this parent
435      */

436     public List removeContent() {
437         List old = new ArrayList(content);
438         content.clear();
439         return old;
440     }
441
442     /**
443      * Remove all child content from this parent matching the supplied filter.
444      *
445      * @param filter filter to select which content to remove
446      * @return list of the old children detached from this parent
447      */

448     public List removeContent(Filter filter) {
449         List old = new ArrayList();
450         Iterator itr = content.getView(filter).iterator();
451         while (itr.hasNext()) {
452             Content child = (Content) itr.next();
453             old.add(child);
454             itr.remove();
455         }
456         return old;
457     }
458
459     /**
460      * This sets the content of the <code>Document</code>. The supplied
461      * List should contain only objects of type <code>Element</code>,
462      * <code>Comment</code>, and <code>ProcessingInstruction</code>.
463      *
464      * <p>
465      * When all objects in the supplied List are legal and before the new
466      * content is added, all objects in the old content will have their
467      * parentage set to null (no parent) and the old content list will be
468      * cleared. This has the effect that any active list (previously obtained
469      * with a call to {@link #getContent}) will also
470      * change to reflect the new content. In addition, all objects in the
471      * supplied List will have their parentage set to this document, but the
472      * List itself will not be "live" and further removals and additions will
473      * have no effect on this document content. If the user wants to continue
474      * working with a "live" list, then a call to setContent should be
475      * followed by a call to {@link #getContent} to
476      * obtain a "live" version of the content.
477      * </p>
478      *
479      * <p>
480      * Passing a null or empty List clears the existing content.
481      * </p>
482      *
483      * <p>
484      * In event of an exception the original content will be unchanged and
485      * the objects in the supplied content will be unaltered.
486      * </p>
487      *
488      * @param newContent <code>List</code> of content to set
489      * @return this document modified
490      * @throws IllegalAddException if the List contains objects of
491      * illegal types or with existing parentage.
492      */

493     public Document setContent(Collection newContent) {
494         content.clearAndSet(newContent);
495         return this;
496     }
497
498     /**
499      *
500      * <p>
501      * Sets the effective URI from which this document was loaded,
502      * and against which relative URLs in this document will be resolved.
503      * </p>
504      *
505      * @param uri the base URI of this document
506      */

507     public final void setBaseURI(String JavaDoc uri) {
508         this.baseURI = uri; // XXX We don't check the URI
509
}
510
511     /**
512      * <p>
513      * Returns the URI from which this document was loaded,
514      * or null if this is not known.
515      * </p>
516      *
517      * @return the base URI of this document
518      */

519     public final String JavaDoc getBaseURI() {
520         return baseURI;
521     }
522
523     /*
524      * Replace the current child the given index with the supplied child.
525      * <p>
526      * In event of an exception the original content will be unchanged and
527      * the supplied child will be unaltered.
528      * </p>
529      *
530      * @param index - index of child to replace.
531      * @param child - child to add.
532      * @throws IllegalAddException if the supplied child is already attached
533      * or not legal content for this parent.
534      * @throws IndexOutOfBoundsException if index is negative or greater
535      * than the current number of children.
536      */

537     public Document setContent(int index, Content child) {
538         content.set(index, child);
539         return this;
540     }
541
542     /**
543      * Replace the child at the given index whith the supplied
544      * collection.
545      * <p>
546      * In event of an exception the original content will be unchanged and
547      * the content in the supplied collection will be unaltered.
548      * </p>
549      *
550      * @param index - index of child to replace.
551      * @param collection - collection of content to add.
552      * @return object on which the method was invoked
553      * @throws IllegalAddException if the collection contains objects of
554      * illegal types.
555      * @throws IndexOutOfBoundsException if index is negative or greater
556      * than the current number of children.
557      */

558     public Document setContent(int index, Collection collection) {
559         content.remove(index);
560         content.addAll(index, collection);
561         return this;
562     }
563
564     public boolean removeContent(Content child) {
565         return content.remove(child);
566     }
567
568     public Content removeContent(int index) {
569         return (Content) content.remove(index);
570     }
571
572     /**
573      * Set this document's content to be the supplied child.
574      * <p>
575      * If the supplied child is legal content for a Document and before
576      * it is added, all content in the current content list will
577      * be cleared and all current children will have their parentage set to
578      * null.
579      * <p>
580      * This has the effect that any active list (previously obtained with
581      * a call to one of the {@link #getContent} methods will also change
582      * to reflect the new content. In addition, all content in the supplied
583      * collection will have their parentage set to this Document. If the user
584      * wants to continue working with a <b>"live"</b> list of this Document's
585      * child, then a call to setContent should be followed by a call to one
586      * of the {@link #getContent} methods to obtain a <b>"live"</b>
587      * version of the children.
588      * <p>
589      * Passing a null child clears the existing content.
590      * <p>
591      * In event of an exception the original content will be unchanged and
592      * the supplied child will be unaltered.
593      *
594      * @param child new content to replace existing content
595      * @return the parent on which the method was called
596      * @throws IllegalAddException if the supplied child is already attached
597      * or not legal content for this parent
598      */

599     public Document setContent(Content child) {
600         content.clear();
601         content.add(child);
602         return this;
603     }
604
605     /**
606      * This returns a <code>String</code> representation of the
607      * <code>Document</code>, suitable for debugging. If the XML
608      * representation of the <code>Document</code> is desired,
609      * {@link org.jdom.output.XMLOutputter#outputString(Document)}
610      * should be used.
611      *
612      * @return <code>String</code> - information about the
613      * <code>Document</code>
614      */

615     public String JavaDoc toString() {
616         StringBuffer JavaDoc stringForm = new StringBuffer JavaDoc()
617             .append("[Document: ");
618
619         DocType docType = getDocType();
620         if (docType != null) {
621             stringForm.append(docType.toString())
622                       .append(", ");
623         } else {
624             stringForm.append(" No DOCTYPE declaration, ");
625         }
626
627         Element rootElement = getRootElement();
628         if (rootElement != null) {
629             stringForm.append("Root is ")
630                       .append(rootElement.toString());
631         } else {
632             stringForm.append(" No root element"); // shouldn't happen
633
}
634
635         stringForm.append("]");
636
637         return stringForm.toString();
638     }
639
640     /**
641      * This tests for equality of this <code>Document</code> to the supplied
642      * <code>Object</code>.
643      *
644      * @param ob <code>Object</code> to compare to
645      * @return <code>boolean</code> whether the <code>Document</code> is
646      * equal to the supplied <code>Object</code>
647      */

648     public final boolean equals(Object JavaDoc ob) {
649         return (ob == this);
650     }
651
652     /**
653      * This returns the hash code for this <code>Document</code>.
654      *
655      * @return <code>int</code> hash code
656      */

657     public final int hashCode() {
658         return super.hashCode();
659     }
660
661     /**
662      * This will return a deep clone of this <code>Document</code>.
663      *
664      * @return <code>Object</code> clone of this <code>Document</code>
665      */

666     public Object JavaDoc clone() {
667         Document doc = null;
668
669         try {
670             doc = (Document) super.clone();
671         } catch (CloneNotSupportedException JavaDoc ce) {
672             // Can't happen
673
}
674
675         // The clone has a reference to this object's content list, so
676
// owerwrite with a empty list
677
doc.content = new ContentList(doc);
678
679         // Add the cloned content to clone
680

681         for (int i = 0; i < content.size(); i++) {
682             Object JavaDoc obj = content.get(i);
683             if (obj instanceof Element) {
684                 Element element = (Element)((Element)obj).clone();
685                 doc.content.add(element);
686             }
687             else if (obj instanceof Comment) {
688                 Comment comment = (Comment)((Comment)obj).clone();
689                 doc.content.add(comment);
690             }
691             else if (obj instanceof ProcessingInstruction) {
692                 ProcessingInstruction pi = (ProcessingInstruction)
693                            ((ProcessingInstruction)obj).clone();
694                 doc.content.add(pi);
695             }
696             else if (obj instanceof DocType) {
697                 DocType dt = (DocType) ((DocType)obj).clone();
698                 doc.content.add(dt);
699             }
700         }
701
702         return doc;
703     }
704
705     /**
706      * Returns an iterator that walks over all descendants in document order.
707      *
708      * @return an iterator to walk descendants
709      */

710     public Iterator getDescendants() {
711         return new DescendantIterator(this);
712     }
713
714     /**
715      * Returns an iterator that walks over all descendants in document order
716      * applying the Filter to return only elements that match the filter rule.
717      * With filters you can match only Elements, only Comments, Elements or
718      * Comments, only Elements with a given name and/or prefix, and so on.
719      *
720      * @param filter filter to select which descendants to see
721      * @return an iterator to walk descendants within a filter
722      */

723     public Iterator getDescendants(Filter filter) {
724         return new FilterIterator(new DescendantIterator(this), filter);
725     }
726
727     public Parent getParent() {
728         return null; // documents never have parents
729
}
730
731
732
733     /**
734      * @see org.jdom.Parent#getDocument()
735      */

736     public Document getDocument() {
737         return this;
738     }
739
740     /**
741      * Assigns an arbitrary object to be associated with this document under
742      * the given "id" string. Null values are permitted. Strings beginning
743      * with "http://www.jdom.org/ are reserved for JDOM use.
744      *
745      * @param id the id of the stored object
746      * @param value the object to store
747      */

748     public void setProperty(String JavaDoc id, Object JavaDoc value) {
749         if (propertyMap == null) {
750             propertyMap = new HashMap();
751         }
752         propertyMap.put(id, value);
753     }
754
755     /**
756      * Returns the object associated with this document under the given "id"
757      * string, or null if there is no binding or if the binding explicitly
758      * stored a null value.
759      *
760      * @param id the id of the stored object to return
761      * @return the object associated with the given id
762      */

763     public Object JavaDoc getProperty(String JavaDoc id) {
764         if (propertyMap == null) return null;
765         return propertyMap.get(id);
766     }
767 }
768
Popular Tags