KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > thoughtworks > xstream > io > xml > SaxWriter


1 package com.thoughtworks.xstream.io.xml;
2
3 import com.thoughtworks.xstream.XStream;
4 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
5 import com.thoughtworks.xstream.io.StreamException;
6 import org.xml.sax.ContentHandler JavaDoc;
7 import org.xml.sax.DTDHandler JavaDoc;
8 import org.xml.sax.EntityResolver JavaDoc;
9 import org.xml.sax.ErrorHandler JavaDoc;
10 import org.xml.sax.InputSource JavaDoc;
11 import org.xml.sax.SAXException JavaDoc;
12 import org.xml.sax.SAXNotRecognizedException JavaDoc;
13 import org.xml.sax.SAXNotSupportedException JavaDoc;
14 import org.xml.sax.XMLReader JavaDoc;
15 import org.xml.sax.helpers.AttributesImpl JavaDoc;
16
17 import java.util.ArrayList JavaDoc;
18 import java.util.Collections JavaDoc;
19 import java.util.HashMap JavaDoc;
20 import java.util.Iterator JavaDoc;
21 import java.util.LinkedList JavaDoc;
22 import java.util.List JavaDoc;
23 import java.util.Map JavaDoc;
24
25 /**
26  * A SAX {@link org.xml.sax.XMLReader parser} that acts as an XStream
27  * {@link HierarchicalStreamWriter} to enable direct generation of
28  * a SAX event flow from the XStream serialization of a list of
29  * list of Java objects.
30  * <p/>
31  * As a custom SAX parser, this class ignores the arguments of the
32  * two standard parse methods ({@link #parse(java.lang.String)} and
33  * {@link #parse(org.xml.sax.InputSource)}) but relies on a
34  * proprietary SAX property {@link #SOURCE_OBJECT_LIST_PROPERTY} to
35  * define the list of objects to serialize.</p>
36  * <p/>
37  * Configuration of this SAX parser is achieved through the standard
38  * {@link #setProperty SAX property mecanism}. While specific setter
39  * methods require direct access to the parser instance, SAX
40  * properties support configuration settings to be propagated through
41  * a chain of {@link org.xml.sax.XMLFilter filters} down to the
42  * underlying parser object.</p>
43  * <p/>
44  * This mecanism shall be used to configure the
45  * {@link #SOURCE_OBJECT_LIST_PROPERTY objects to be serialized} as
46  * well as the {@link #CONFIGURED_XSTREAM_PROPERTY XStream facade}.</p>
47  *
48  * @author Laurent Bihanic
49  */

50 public final class SaxWriter implements HierarchicalStreamWriter, XMLReader JavaDoc {
51     /**
52      * The {@link #setProperty SAX property} to configure the XStream
53      * facade to be used for object serialization. If the property
54      * is not set, a new XStream facade will be allocated for each
55      * parse.
56      */

57     public final static String JavaDoc CONFIGURED_XSTREAM_PROPERTY =
58             "http://com.thoughtworks.xstream/sax/property/configured-xstream";
59
60     /**
61      * The {@link #setProperty SAX property} to configure the list of
62      * Java objects to serialize. Setting this property prior
63      * invoking one of the parse() methods is mandatory.
64      *
65      * @see #parse(java.lang.String)
66      * @see #parse(org.xml.sax.InputSource)
67      */

68     public final static String JavaDoc SOURCE_OBJECT_LIST_PROPERTY =
69             "http://com.thoughtworks.xstream/sax/property/source-object-list";
70
71     //=========================================================================
72
// SAX XMLReader interface support
73
//=========================================================================
74

75     /**
76      * The SAX EntityResolver associated to this XMLReader.
77      */

78     private EntityResolver JavaDoc entityResolver = null;
79
80     /**
81      * The SAX DTDHandler associated to this XMLReader.
82      */

83     private DTDHandler JavaDoc dtdHandler = null;
84
85     /**
86      * The SAX ContentHandler associated to this XMLReader.
87      */

88     private ContentHandler JavaDoc contentHandler = null;
89
90     /**
91      * The SAX ErrorHandler associated to this XMLReader.
92      */

93     private ErrorHandler JavaDoc errorHandler = null;
94
95     /**
96      * The SAX features defined for this XMLReader.
97      * <p/>
98      * This class does not define any feature (yet) and ignores
99      * the SAX mandatory feature. Thus, this member is present
100      * only to support the mandatory feature setting and retrieval
101      * logic defined by SAX.</p>
102      */

103     private Map JavaDoc features = new HashMap JavaDoc();
104
105     /**
106      * The SAX properties defined for this XMLReader.
107      */

108     private final Map JavaDoc properties = new HashMap JavaDoc();
109
110     private final boolean includeEnclosingDocument;
111
112     public SaxWriter(boolean includeEnclosingDocument) {
113         this.includeEnclosingDocument = includeEnclosingDocument;
114     }
115
116     public SaxWriter() {
117         this(true);
118     }
119
120     //-------------------------------------------------------------------------
121
// Configuration
122
//-------------------------------------------------------------------------
123

124     /**
125      * Sets the state of a feature.
126      * <p/>
127      * The feature name is any fully-qualified URI.</p>
128      * <p/>
129      * All XMLReaders are required to support setting
130      * <code>http://xml.org/sax/features/namespaces</code> to
131      * <code>true</code> and
132      * <code>http://xml.org/sax/features/namespace-prefixes</code> to
133      * <code>false</code>.</p>
134      * <p/>
135      * Some feature values may be immutable or mutable only
136      * in specific contexts, such as before, during, or after
137      * a parse.</p>
138      * <p/>
139      * <strong>Note</strong>: This implemention only supports the two
140      * mandatory SAX features.</p>
141      *
142      * @param name the feature name, which is a fully-qualified URI.
143      * @param value the requested state of the feature (true or false).
144      * @throws SAXNotRecognizedException when the XMLReader does not
145      * recognize the feature name.
146      * @see #getFeature
147      */

148     public void setFeature(String JavaDoc name, boolean value)
149             throws SAXNotRecognizedException JavaDoc {
150         if ((name.equals("http://xml.org/sax/features/namespaces")) ||
151                 (name.equals("http://xml.org/sax/features/namespace-prefixes"))) {
152             this.features.put(name, new Boolean JavaDoc(value));
153         } else {
154             throw new SAXNotRecognizedException JavaDoc(name);
155         }
156     }
157
158     /**
159      * Looks up the value of a feature.
160      * <p/>
161      * The feature name is any fully-qualified URI. It is
162      * possible for an XMLReader to recognize a feature name but
163      * to be unable to return its value; this is especially true
164      * in the case of an adapter for a SAX1 Parser, which has
165      * no way of knowing whether the underlying parser is
166      * performing validation or expanding external entities.</p>
167      * <p/>
168      * All XMLReaders are required to recognize the
169      * <code>http://xml.org/sax/features/namespaces</code> and the
170      * <code>http://xml.org/sax/features/namespace-prefixes</code> feature
171      * names.</p>
172      * <p/>
173      * Some feature values may be available only in specific
174      * contexts, such as before, during, or after a parse.</p>
175      * <p/>
176      * Implementors are free (and encouraged) to invent their own
177      * features, using names built on their own URIs.</p>
178      *
179      * @param name the feature name, which is a fully-qualified URI.
180      * @return the current state of the feature (true or false).
181      * @throws SAXNotRecognizedException when the XMLReader does not
182      * recognize the feature name.
183      * @see #setFeature
184      */

185     public boolean getFeature(String JavaDoc name)
186             throws SAXNotRecognizedException JavaDoc {
187         if ((name.equals("http://xml.org/sax/features/namespaces")) ||
188                 (name.equals("http://xml.org/sax/features/namespace-prefixes"))) {
189             Boolean JavaDoc value = (Boolean JavaDoc) (this.features.get(name));
190
191             if (value == null) {
192                 value = Boolean.FALSE;
193             }
194             return value.booleanValue();
195         } else {
196             throw new SAXNotRecognizedException JavaDoc(name);
197         }
198     }
199
200     /**
201      * Sets the value of a property.
202      * <p/>
203      * The property name is any fully-qualified URI. It is
204      * possible for an XMLReader to recognize a property name but
205      * to be unable to set its value.</p>
206      * <p/>
207      * XMLReaders are not required to recognize setting any
208      * specific property names, though a core set is provided with
209      * SAX2.</p>
210      * <p/>
211      * Some property values may be immutable or mutable only
212      * in specific contexts, such as before, during, or after
213      * a parse.</p>
214      * <p/>
215      * This method is also the standard mechanism for setting
216      * extended handlers.</p>
217      * <p/>
218      * <strong>Note</strong>: This implemention only supports two
219      * (proprietary) properties: {@link #CONFIGURED_XSTREAM_PROPERTY}
220      * and {@link #SOURCE_OBJECT_LIST_PROPERTY}.</p>
221      *
222      * @param name the property name, which is a fully-qualified URI.
223      * @param value the requested value for the property.
224      * @throws SAXNotRecognizedException when the XMLReader does not
225      * recognize the property name.
226      * @throws SAXNotSupportedException when the XMLReader recognizes
227      * the property name but cannot set
228      * the requested value.
229      * @see #getProperty
230      */

231     public void setProperty(String JavaDoc name, Object JavaDoc value)
232             throws SAXNotRecognizedException JavaDoc,
233             SAXNotSupportedException JavaDoc {
234         if (name.equals(CONFIGURED_XSTREAM_PROPERTY)) {
235             if (!(value instanceof XStream)) {
236                 throw new SAXNotSupportedException JavaDoc("Value for property \"" +
237                         CONFIGURED_XSTREAM_PROPERTY +
238                         "\" must be a non-null XStream object");
239             }
240         } else if (name.equals(SOURCE_OBJECT_LIST_PROPERTY)) {
241             if (value instanceof List JavaDoc) {
242                 List JavaDoc list = (List JavaDoc) value;
243
244                 if (list.isEmpty()) {
245                     throw new SAXNotSupportedException JavaDoc("Value for property \"" +
246                             SOURCE_OBJECT_LIST_PROPERTY +
247                             "\" shall not be an empty list");
248                 } else {
249                     // Perform a copy of the list to prevent the application to
250
// modify its content while the parse is being performed.
251
value = Collections.unmodifiableList(new ArrayList JavaDoc(list));
252                 }
253             } else {
254                 throw new SAXNotSupportedException JavaDoc("Value for property \"" +
255                         SOURCE_OBJECT_LIST_PROPERTY +
256                         "\" must be a non-null List object");
257             }
258         } else {
259             throw new SAXNotRecognizedException JavaDoc(name);
260         }
261         this.properties.put(name, value);
262     }
263
264     /**
265      * Looks up the value of a property.
266      * <p/>
267      * The property name is any fully-qualified URI. It is
268      * possible for an XMLReader to recognize a property name but
269      * to be unable to return its state.</p>
270      * <p/>
271      * XMLReaders are not required to recognize any specific
272      * property names, though an initial core set is documented for
273      * SAX2.</p>
274      * <p/>
275      * Some property values may be available only in specific
276      * contexts, such as before, during, or after a parse.</p>
277      * <p/>
278      * Implementors are free (and encouraged) to invent their own properties,
279      * using names built on their own URIs.</p>
280      *
281      * @param name the property name, which is a fully-qualified URI.
282      * @return the current value of the property.
283      * @throws SAXNotRecognizedException when the XMLReader does not
284      * recognize the property name.
285      * @see #getProperty
286      */

287     public Object JavaDoc getProperty(String JavaDoc name)
288             throws SAXNotRecognizedException JavaDoc {
289         if ((name.equals(CONFIGURED_XSTREAM_PROPERTY)) ||
290                 (name.equals(SOURCE_OBJECT_LIST_PROPERTY))) {
291             return this.properties.get(name);
292         } else {
293             throw new SAXNotRecognizedException JavaDoc(name);
294         }
295     }
296
297     //---------------------------------------------------------------------
298
// Event handlers
299
//---------------------------------------------------------------------
300

301     /**
302      * Allows an application to register an entity resolver.
303      * <p/>
304      * If the application does not register an entity resolver,
305      * the XMLReader will perform its own default resolution.</p>
306      * <p/>
307      * Applications may register a new or different resolver in the
308      * middle of a parse, and the SAX parser must begin using the new
309      * resolver immediately.</p>
310      *
311      * @param resolver the entity resolver.
312      * @throws NullPointerException if the resolver argument is
313      * <code>null</code>.
314      * @see #getEntityResolver
315      */

316     public void setEntityResolver(EntityResolver JavaDoc resolver) {
317         if (resolver == null) {
318             throw new NullPointerException JavaDoc("resolver");
319         }
320         this.entityResolver = resolver;
321         return;
322     }
323
324     /**
325      * Returns the current entity resolver.
326      *
327      * @return the current entity resolver, or <code>null</code> if none
328      * has been registered.
329      * @see #setEntityResolver
330      */

331     public EntityResolver JavaDoc getEntityResolver() {
332         return this.entityResolver;
333     }
334
335     /**
336      * Allows an application to register a DTD event handler.
337      * <p/>
338      * If the application does not register a DTD handler, all DTD
339      * events reported by the SAX parser will be silently ignored.</p>
340      * <p/>
341      * Applications may register a new or different handler in the
342      * middle of a parse, and the SAX parser must begin using the new
343      * handler immediately.</p>
344      *
345      * @param handler the DTD handler.
346      * @throws NullPointerException if the handler argument is
347      * <code>null</code>.
348      * @see #getDTDHandler
349      */

350     public void setDTDHandler(DTDHandler JavaDoc handler) {
351         if (handler == null) {
352             throw new NullPointerException JavaDoc("handler");
353         }
354         this.dtdHandler = handler;
355         return;
356     }
357
358     /**
359      * Returns the current DTD handler.
360      *
361      * @return the current DTD handler, or <code>null</code> if none
362      * has been registered.
363      * @see #setDTDHandler
364      */

365     public DTDHandler JavaDoc getDTDHandler() {
366         return this.dtdHandler;
367     }
368
369     /**
370      * Allows an application to register a content event handler.
371      * <p/>
372      * If the application does not register a content handler, all
373      * content events reported by the SAX parser will be silently
374      * ignored.</p>
375      * <p/>
376      * Applications may register a new or different handler in the
377      * middle of a parse, and the SAX parser must begin using the new
378      * handler immediately.</p>
379      *
380      * @param handler the content handler.
381      * @throws NullPointerException if the handler argument is
382      * <code>null</code>.
383      * @see #getContentHandler
384      */

385     public void setContentHandler(ContentHandler JavaDoc handler) {
386         if (handler == null) {
387             throw new NullPointerException JavaDoc("handler");
388         }
389         this.contentHandler = handler;
390         return;
391     }
392
393     /**
394      * Returns the current content handler.
395      *
396      * @return the current content handler, or <code>null</code> if none
397      * has been registered.
398      * @see #setContentHandler
399      */

400     public ContentHandler JavaDoc getContentHandler() {
401         return this.contentHandler;
402     }
403
404     /**
405      * Allows an application to register an error event handler.
406      * <p/>
407      * If the application does not register an error handler, all
408      * error events reported by the SAX parser will be silently
409      * ignored; however, normal processing may not continue. It is
410      * highly recommended that all SAX applications implement an
411      * error handler to avoid unexpected bugs.</p>
412      * <p/>
413      * Applications may register a new or different handler in the
414      * middle of a parse, and the SAX parser must begin using the new
415      * handler immediately.</p>
416      *
417      * @param handler the error handler.
418      * @throws NullPointerException if the handler argument is
419      * <code>null</code>.
420      * @see #getErrorHandler
421      */

422     public void setErrorHandler(ErrorHandler JavaDoc handler) {
423         if (handler == null) {
424             throw new NullPointerException JavaDoc("handler");
425         }
426         this.errorHandler = handler;
427         return;
428     }
429
430     /**
431      * Returns the current error handler.
432      *
433      * @return the current error handler, or <code>null</code> if none
434      * has been registered.
435      * @see #setErrorHandler
436      */

437     public ErrorHandler JavaDoc getErrorHandler() {
438         return this.errorHandler;
439     }
440
441     //---------------------------------------------------------------------
442
// Parsing
443
//---------------------------------------------------------------------
444

445     /**
446      * Parses an XML document from a system identifier (URI).
447      * <p/>
448      * This method is a shortcut for the common case of reading a
449      * document from a system identifier. It is the exact
450      * equivalent of the following:</p>
451      * <blockquote>
452      * <pre>
453      * parse(new InputSource(systemId));
454      * </pre>
455      * </blockquote>
456      * <p/>
457      * If the system identifier is a URL, it must be fully resolved
458      * by the application before it is passed to the parser.</p>
459      * <p/>
460      * <strong>Note</strong>: As a custom SAX parser, this class
461      * ignores the <code>systemId</code> argument of this method
462      * and relies on the proprietary SAX property
463      * {@link #SOURCE_OBJECT_LIST_PROPERTY}) to define the list of
464      * objects to serialize.</p>
465      *
466      * @param systemId the system identifier (URI).
467      * @throws SAXException Any SAX exception, possibly wrapping
468      * another exception.
469      * @see #parse(org.xml.sax.InputSource)
470      */

471     public void parse(String JavaDoc systemId) throws SAXException JavaDoc {
472         this.parse();
473     }
474
475     /**
476      * Parse an XML document.
477      * <p/>
478      * The application can use this method to instruct the XML
479      * reader to begin parsing an XML document from any valid input
480      * source (a character stream, a byte stream, or a URI).</p>
481      * <p/>
482      * Applications may not invoke this method while a parse is in
483      * progress (they should create a new XMLReader instead for each
484      * nested XML document). Once a parse is complete, an
485      * application may reuse the same XMLReader object, possibly
486      * with a different input source.</p>
487      * <p/>
488      * During the parse, the XMLReader will provide information
489      * about the XML document through the registered event
490      * handlers.</p>
491      * <p/>
492      * This method is synchronous: it will not return until parsing
493      * has ended. If a client application wants to terminate
494      * parsing early, it should throw an exception.</p>
495      * <p/>
496      * <strong>Note</strong>: As a custom SAX parser, this class
497      * ignores the <code>source</code> argument of this method
498      * and relies on the proprietary SAX property
499      * {@link #SOURCE_OBJECT_LIST_PROPERTY}) to define the list of
500      * objects to serialize.</p>
501      *
502      * @param input The input source for the top-level of the
503      * XML document.
504      * @throws SAXException Any SAX exception, possibly wrapping
505      * another exception.
506      * @see org.xml.sax.InputSource
507      * @see #parse(java.lang.String)
508      * @see #setEntityResolver
509      * @see #setDTDHandler
510      * @see #setContentHandler
511      * @see #setErrorHandler
512      */

513     public void parse(InputSource JavaDoc input) throws SAXException JavaDoc {
514         this.parse();
515     }
516
517     /**
518      * Serializes the Java objects of the configured list into a flow
519      * of SAX events.
520      *
521      * @throws SAXException if the configured object list is invalid
522      * or object serialization failed.
523      */

524     private void parse() throws SAXException JavaDoc {
525         XStream xstream = (XStream) (this.properties.get(CONFIGURED_XSTREAM_PROPERTY));
526         if (xstream == null) {
527             xstream = new XStream();
528         }
529
530         List JavaDoc source = (List JavaDoc) (this.properties.get(SOURCE_OBJECT_LIST_PROPERTY));
531         if ((source == null) || (source.isEmpty())) {
532             throw new SAXException JavaDoc("Missing or empty source object list. Setting property \"" +
533                     SOURCE_OBJECT_LIST_PROPERTY + "\" is mandatory");
534         }
535
536         try {
537             this.startDocument(true);
538             for (Iterator JavaDoc i = source.iterator(); i.hasNext();) {
539                 xstream.marshal(i.next(), this);
540             }
541             this.endDocument(true);
542         } catch (StreamException e) {
543             if (e.getCause() instanceof SAXException JavaDoc) {
544                 throw (SAXException JavaDoc) (e.getCause());
545             } else {
546                 throw new SAXException JavaDoc(e);
547             }
548         }
549     }
550
551
552     //=========================================================================
553
// XStream HierarchicalStreamWriter interface support
554
//=========================================================================
555

556     private int depth = 0;
557     private List JavaDoc elementStack = new LinkedList JavaDoc();
558     private char[] buffer = new char[128];
559     private boolean startTagInProgress = false;
560     private final AttributesImpl JavaDoc attributeList = new AttributesImpl JavaDoc();
561
562     public void startNode(String JavaDoc name) {
563         try {
564             if (this.depth != 0) {
565                 this.flushStartTag();
566             } else if (includeEnclosingDocument) {
567                 this.startDocument(false);
568             }
569             this.elementStack.add(0, name);
570
571             this.startTagInProgress = true;
572             this.depth++;
573         } catch (SAXException JavaDoc e) {
574             throw new StreamException(e);
575         }
576     }
577
578     public void addAttribute(String JavaDoc name, String JavaDoc value) {
579         if (this.startTagInProgress) {
580             this.attributeList.addAttribute("", name, name, "CDATA", value);
581         } else {
582             throw new StreamException(new IllegalStateException JavaDoc("No startElement being processed"));
583         }
584     }
585
586     public void setValue(String JavaDoc text) {
587         try {
588             this.flushStartTag();
589
590             int lg = text.length();
591             if (lg > buffer.length) {
592                 buffer = new char[lg];
593             }
594             text.getChars(0, lg, buffer, 0);
595
596             this.contentHandler.characters(buffer, 0, lg);
597         } catch (SAXException JavaDoc e) {
598             throw new StreamException(e);
599         }
600     }
601
602     public void endNode() {
603         try {
604             this.flushStartTag();
605
606             String JavaDoc tagName = (String JavaDoc) (this.elementStack.remove(0));
607
608             this.contentHandler.endElement("", tagName, tagName);
609
610             this.depth--;
611             if (this.depth == 0 && includeEnclosingDocument) {
612                 this.endDocument(false);
613             }
614         } catch (SAXException JavaDoc e) {
615             throw new StreamException(e);
616         }
617     }
618
619     /**
620      * Fires the SAX startDocument event towards the configured
621      * ContentHandler.
622      *
623      * @param multiObjectMode whether serialization of several
624      * object will be merge into a single
625      * SAX document.
626      * @throws SAXException if thrown by the ContentHandler.
627      */

628     private void startDocument(boolean multiObjectMode) throws SAXException JavaDoc {
629         if (this.depth == 0) {
630             // Notify contentHandler of document start.
631
this.contentHandler.startDocument();
632
633             if (multiObjectMode) {
634                 // Prevent marshalling of each object to fire its own
635
// start/endDocument events.
636
this.depth++;
637             }
638         }
639     }
640
641     /**
642      * Fires the SAX endDocument event towards the configured
643      * ContentHandler.
644      *
645      * @param multiObjectMode whether serialization of several
646      * object will be merge into a single
647      * SAX document.
648      * @throws SAXException if thrown by the ContentHandler.
649      */

650     private void endDocument(boolean multiObjectMode) throws SAXException JavaDoc {
651         if ((this.depth == 0) || ((this.depth == 1) && (multiObjectMode))) {
652             this.contentHandler.endDocument();
653             this.depth = 0;
654         }
655     }
656
657     /**
658      * Fires any pending SAX startElement event towards the
659      * configured ContentHandler.
660      *
661      * @throws SAXException if thrown by the ContentHandler.
662      */

663     private void flushStartTag() throws SAXException JavaDoc {
664         if (this.startTagInProgress) {
665             String JavaDoc tagName = (String JavaDoc) (this.elementStack.get(0));
666
667             this.contentHandler.startElement("", tagName,
668                     tagName, this.attributeList);
669             this.attributeList.clear();
670             this.startTagInProgress = false;
671         }
672     }
673
674     public void flush() {
675         // don't need to do anything
676
}
677
678     public void close() {
679         // don't need to do anything
680
}
681
682     public HierarchicalStreamWriter underlyingWriter() {
683         return this;
684     }
685 }
686
687
Popular Tags