001: /*
002: * Copyright (C) 2004, 2005, 2006 Joe Walnes.
003: * Copyright (C) 2006, 2007 XStream Committers.
004: * All rights reserved.
005: *
006: * The software in this package is published under the terms of the BSD
007: * style license a copy of which has been included with this distribution in
008: * the LICENSE.txt file.
009: *
010: * Created on 14. August 2004 by Joe Walnes
011: */
012: package com.thoughtworks.xstream.io.xml;
013:
014: import com.thoughtworks.xstream.XStream;
015: import com.thoughtworks.xstream.io.StreamException;
016:
017: import org.xml.sax.ContentHandler;
018: import org.xml.sax.DTDHandler;
019: import org.xml.sax.EntityResolver;
020: import org.xml.sax.ErrorHandler;
021: import org.xml.sax.InputSource;
022: import org.xml.sax.SAXException;
023: import org.xml.sax.SAXNotRecognizedException;
024: import org.xml.sax.SAXNotSupportedException;
025: import org.xml.sax.XMLReader;
026: import org.xml.sax.helpers.AttributesImpl;
027:
028: import java.util.ArrayList;
029: import java.util.Collections;
030: import java.util.HashMap;
031: import java.util.Iterator;
032: import java.util.LinkedList;
033: import java.util.List;
034: import java.util.Map;
035:
036: /**
037: * A SAX {@link org.xml.sax.XMLReader parser} that acts as an XStream
038: * {@link com.thoughtworks.xstream.io.HierarchicalStreamWriter} to enable direct generation of a
039: * SAX event flow from the XStream serialization of a list of list of Java objects. <p/> As a
040: * custom SAX parser, this class ignores the arguments of the two standard parse methods ({@link #parse(java.lang.String)}
041: * and {@link #parse(org.xml.sax.InputSource)}) but relies on a proprietary SAX property
042: * {@link #SOURCE_OBJECT_LIST_PROPERTY} to define the list of objects to serialize.
043: * </p>
044: * <p/> Configuration of this SAX parser is achieved through the standard
045: * {@link #setProperty SAX property mechanism}. While specific setter methods require direct
046: * access to the parser instance, SAX properties support configuration settings to be propagated
047: * through a chain of {@link org.xml.sax.XMLFilter filters} down to the underlying parser
048: * object.
049: * </p>
050: * <p/> This mechanism shall be used to configure the
051: * {@link #SOURCE_OBJECT_LIST_PROPERTY objects to be serialized} as well as the
052: * {@link #CONFIGURED_XSTREAM_PROPERTY XStream facade}.
053: * </p>
054: *
055: * @author Laurent Bihanic
056: */
057: public final class SaxWriter extends AbstractXmlWriter implements
058: XMLReader {
059: /**
060: * The {@link #setProperty SAX property} to configure the XStream
061: * facade to be used for object serialization. If the property
062: * is not set, a new XStream facade will be allocated for each
063: * parse.
064: */
065: public final static String CONFIGURED_XSTREAM_PROPERTY = "http://com.thoughtworks.xstream/sax/property/configured-xstream";
066:
067: /**
068: * The {@link #setProperty SAX property} to configure the list of
069: * Java objects to serialize. Setting this property prior
070: * invoking one of the parse() methods is mandatory.
071: *
072: * @see #parse(java.lang.String)
073: * @see #parse(org.xml.sax.InputSource)
074: */
075: public final static String SOURCE_OBJECT_LIST_PROPERTY = "http://com.thoughtworks.xstream/sax/property/source-object-list";
076:
077: //=========================================================================
078: // SAX XMLReader interface support
079: //=========================================================================
080:
081: /**
082: * The SAX EntityResolver associated to this XMLReader.
083: */
084: private EntityResolver entityResolver = null;
085:
086: /**
087: * The SAX DTDHandler associated to this XMLReader.
088: */
089: private DTDHandler dtdHandler = null;
090:
091: /**
092: * The SAX ContentHandler associated to this XMLReader.
093: */
094: private ContentHandler contentHandler = null;
095:
096: /**
097: * The SAX ErrorHandler associated to this XMLReader.
098: */
099: private ErrorHandler errorHandler = null;
100:
101: /**
102: * The SAX features defined for this XMLReader.
103: * <p/>
104: * This class does not define any feature (yet) and ignores
105: * the SAX mandatory feature. Thus, this member is present
106: * only to support the mandatory feature setting and retrieval
107: * logic defined by SAX.</p>
108: */
109: private Map features = new HashMap();
110:
111: /**
112: * The SAX properties defined for this XMLReader.
113: */
114: private final Map properties = new HashMap();
115:
116: private final boolean includeEnclosingDocument;
117:
118: public SaxWriter(XmlFriendlyReplacer replacer) {
119: this (true, replacer);
120: }
121:
122: public SaxWriter(boolean includeEnclosingDocument,
123: XmlFriendlyReplacer replacer) {
124: super (replacer);
125: this .includeEnclosingDocument = includeEnclosingDocument;
126: }
127:
128: public SaxWriter(boolean includeEnclosingDocument) {
129: this (includeEnclosingDocument, new XmlFriendlyReplacer());
130: }
131:
132: public SaxWriter() {
133: this (true);
134: }
135:
136: //-------------------------------------------------------------------------
137: // Configuration
138: //-------------------------------------------------------------------------
139:
140: /**
141: * Sets the state of a feature.
142: * <p/>
143: * The feature name is any fully-qualified URI.</p>
144: * <p/>
145: * All XMLReaders are required to support setting
146: * <code>http://xml.org/sax/features/namespaces</code> to
147: * <code>true</code> and
148: * <code>http://xml.org/sax/features/namespace-prefixes</code> to
149: * <code>false</code>.</p>
150: * <p/>
151: * Some feature values may be immutable or mutable only
152: * in specific contexts, such as before, during, or after
153: * a parse.</p>
154: * <p/>
155: * <strong>Note</strong>: This implementation only supports the two
156: * mandatory SAX features.</p>
157: *
158: * @param name the feature name, which is a fully-qualified URI.
159: * @param value the requested state of the feature (true or false).
160: * @throws SAXNotRecognizedException when the XMLReader does not
161: * recognize the feature name.
162: * @see #getFeature
163: */
164: public void setFeature(String name, boolean value)
165: throws SAXNotRecognizedException {
166: if ((name.equals("http://xml.org/sax/features/namespaces"))
167: || (name
168: .equals("http://xml.org/sax/features/namespace-prefixes"))) {
169: this .features.put(name, value ? Boolean.TRUE
170: : Boolean.FALSE); // JDK 1.3 friendly
171: } else {
172: throw new SAXNotRecognizedException(name);
173: }
174: }
175:
176: /**
177: * Looks up the value of a feature.
178: * <p/>
179: * The feature name is any fully-qualified URI. It is
180: * possible for an XMLReader to recognize a feature name but
181: * to be unable to return its value; this is especially true
182: * in the case of an adapter for a SAX1 Parser, which has
183: * no way of knowing whether the underlying parser is
184: * performing validation or expanding external entities.</p>
185: * <p/>
186: * All XMLReaders are required to recognize the
187: * <code>http://xml.org/sax/features/namespaces</code> and the
188: * <code>http://xml.org/sax/features/namespace-prefixes</code> feature
189: * names.</p>
190: * <p/>
191: * Some feature values may be available only in specific
192: * contexts, such as before, during, or after a parse.</p>
193: * <p/>
194: * Implementors are free (and encouraged) to invent their own
195: * features, using names built on their own URIs.</p>
196: *
197: * @param name the feature name, which is a fully-qualified URI.
198: * @return the current state of the feature (true or false).
199: * @throws SAXNotRecognizedException when the XMLReader does not
200: * recognize the feature name.
201: * @see #setFeature
202: */
203: public boolean getFeature(String name)
204: throws SAXNotRecognizedException {
205: if ((name.equals("http://xml.org/sax/features/namespaces"))
206: || (name
207: .equals("http://xml.org/sax/features/namespace-prefixes"))) {
208: Boolean value = (Boolean) (this .features.get(name));
209:
210: if (value == null) {
211: value = Boolean.FALSE;
212: }
213: return value.booleanValue();
214: } else {
215: throw new SAXNotRecognizedException(name);
216: }
217: }
218:
219: /**
220: * Sets the value of a property.
221: * <p/>
222: * The property name is any fully-qualified URI. It is
223: * possible for an XMLReader to recognize a property name but
224: * to be unable to set its value.</p>
225: * <p/>
226: * XMLReaders are not required to recognize setting any
227: * specific property names, though a core set is provided with
228: * SAX2.</p>
229: * <p/>
230: * Some property values may be immutable or mutable only
231: * in specific contexts, such as before, during, or after
232: * a parse.</p>
233: * <p/>
234: * This method is also the standard mechanism for setting
235: * extended handlers.</p>
236: * <p/>
237: * <strong>Note</strong>: This implementation only supports two
238: * (proprietary) properties: {@link #CONFIGURED_XSTREAM_PROPERTY}
239: * and {@link #SOURCE_OBJECT_LIST_PROPERTY}.</p>
240: *
241: * @param name the property name, which is a fully-qualified URI.
242: * @param value the requested value for the property.
243: * @throws SAXNotRecognizedException when the XMLReader does not
244: * recognize the property name.
245: * @throws SAXNotSupportedException when the XMLReader recognizes
246: * the property name but cannot set
247: * the requested value.
248: * @see #getProperty
249: */
250: public void setProperty(String name, Object value)
251: throws SAXNotRecognizedException, SAXNotSupportedException {
252: if (name.equals(CONFIGURED_XSTREAM_PROPERTY)) {
253: if (!(value instanceof XStream)) {
254: throw new SAXNotSupportedException(
255: "Value for property \""
256: + CONFIGURED_XSTREAM_PROPERTY
257: + "\" must be a non-null XStream object");
258: }
259: } else if (name.equals(SOURCE_OBJECT_LIST_PROPERTY)) {
260: if (value instanceof List) {
261: List list = (List) value;
262:
263: if (list.isEmpty()) {
264: throw new SAXNotSupportedException(
265: "Value for property \""
266: + SOURCE_OBJECT_LIST_PROPERTY
267: + "\" shall not be an empty list");
268: } else {
269: // Perform a copy of the list to prevent the application to
270: // modify its content while the parse is being performed.
271: value = Collections.unmodifiableList(new ArrayList(
272: list));
273: }
274: } else {
275: throw new SAXNotSupportedException(
276: "Value for property \""
277: + SOURCE_OBJECT_LIST_PROPERTY
278: + "\" must be a non-null List object");
279: }
280: } else {
281: throw new SAXNotRecognizedException(name);
282: }
283: this .properties.put(name, value);
284: }
285:
286: /**
287: * Looks up the value of a property.
288: * <p/>
289: * The property name is any fully-qualified URI. It is
290: * possible for an XMLReader to recognize a property name but
291: * to be unable to return its state.</p>
292: * <p/>
293: * XMLReaders are not required to recognize any specific
294: * property names, though an initial core set is documented for
295: * SAX2.</p>
296: * <p/>
297: * Some property values may be available only in specific
298: * contexts, such as before, during, or after a parse.</p>
299: * <p/>
300: * Implementors are free (and encouraged) to invent their own properties,
301: * using names built on their own URIs.</p>
302: *
303: * @param name the property name, which is a fully-qualified URI.
304: * @return the current value of the property.
305: * @throws SAXNotRecognizedException when the XMLReader does not
306: * recognize the property name.
307: * @see #getProperty
308: */
309: public Object getProperty(String name)
310: throws SAXNotRecognizedException {
311: if ((name.equals(CONFIGURED_XSTREAM_PROPERTY))
312: || (name.equals(SOURCE_OBJECT_LIST_PROPERTY))) {
313: return this .properties.get(name);
314: } else {
315: throw new SAXNotRecognizedException(name);
316: }
317: }
318:
319: //---------------------------------------------------------------------
320: // Event handlers
321: //---------------------------------------------------------------------
322:
323: /**
324: * Allows an application to register an entity resolver.
325: * <p/>
326: * If the application does not register an entity resolver,
327: * the XMLReader will perform its own default resolution.</p>
328: * <p/>
329: * Applications may register a new or different resolver in the
330: * middle of a parse, and the SAX parser must begin using the new
331: * resolver immediately.</p>
332: *
333: * @param resolver the entity resolver.
334: * @throws NullPointerException if the resolver argument is
335: * <code>null</code>.
336: * @see #getEntityResolver
337: */
338: public void setEntityResolver(EntityResolver resolver) {
339: if (resolver == null) {
340: throw new NullPointerException("resolver");
341: }
342: this .entityResolver = resolver;
343: return;
344: }
345:
346: /**
347: * Returns the current entity resolver.
348: *
349: * @return the current entity resolver, or <code>null</code> if none
350: * has been registered.
351: * @see #setEntityResolver
352: */
353: public EntityResolver getEntityResolver() {
354: return this .entityResolver;
355: }
356:
357: /**
358: * Allows an application to register a DTD event handler.
359: * <p/>
360: * If the application does not register a DTD handler, all DTD
361: * events reported by the SAX parser will be silently ignored.</p>
362: * <p/>
363: * Applications may register a new or different handler in the
364: * middle of a parse, and the SAX parser must begin using the new
365: * handler immediately.</p>
366: *
367: * @param handler the DTD handler.
368: * @throws NullPointerException if the handler argument is
369: * <code>null</code>.
370: * @see #getDTDHandler
371: */
372: public void setDTDHandler(DTDHandler handler) {
373: if (handler == null) {
374: throw new NullPointerException("handler");
375: }
376: this .dtdHandler = handler;
377: return;
378: }
379:
380: /**
381: * Returns the current DTD handler.
382: *
383: * @return the current DTD handler, or <code>null</code> if none
384: * has been registered.
385: * @see #setDTDHandler
386: */
387: public DTDHandler getDTDHandler() {
388: return this .dtdHandler;
389: }
390:
391: /**
392: * Allows an application to register a content event handler.
393: * <p/>
394: * If the application does not register a content handler, all
395: * content events reported by the SAX parser will be silently
396: * ignored.</p>
397: * <p/>
398: * Applications may register a new or different handler in the
399: * middle of a parse, and the SAX parser must begin using the new
400: * handler immediately.</p>
401: *
402: * @param handler the content handler.
403: * @throws NullPointerException if the handler argument is
404: * <code>null</code>.
405: * @see #getContentHandler
406: */
407: public void setContentHandler(ContentHandler handler) {
408: if (handler == null) {
409: throw new NullPointerException("handler");
410: }
411: this .contentHandler = handler;
412: return;
413: }
414:
415: /**
416: * Returns the current content handler.
417: *
418: * @return the current content handler, or <code>null</code> if none
419: * has been registered.
420: * @see #setContentHandler
421: */
422: public ContentHandler getContentHandler() {
423: return this .contentHandler;
424: }
425:
426: /**
427: * Allows an application to register an error event handler.
428: * <p/>
429: * If the application does not register an error handler, all
430: * error events reported by the SAX parser will be silently
431: * ignored; however, normal processing may not continue. It is
432: * highly recommended that all SAX applications implement an
433: * error handler to avoid unexpected bugs.</p>
434: * <p/>
435: * Applications may register a new or different handler in the
436: * middle of a parse, and the SAX parser must begin using the new
437: * handler immediately.</p>
438: *
439: * @param handler the error handler.
440: * @throws NullPointerException if the handler argument is
441: * <code>null</code>.
442: * @see #getErrorHandler
443: */
444: public void setErrorHandler(ErrorHandler handler) {
445: if (handler == null) {
446: throw new NullPointerException("handler");
447: }
448: this .errorHandler = handler;
449: return;
450: }
451:
452: /**
453: * Returns the current error handler.
454: *
455: * @return the current error handler, or <code>null</code> if none
456: * has been registered.
457: * @see #setErrorHandler
458: */
459: public ErrorHandler getErrorHandler() {
460: return this .errorHandler;
461: }
462:
463: //---------------------------------------------------------------------
464: // Parsing
465: //---------------------------------------------------------------------
466:
467: /**
468: * Parses an XML document from a system identifier (URI).
469: * <p/>
470: * This method is a shortcut for the common case of reading a
471: * document from a system identifier. It is the exact
472: * equivalent of the following:</p>
473: * <blockquote>
474: * <pre>
475: * parse(new InputSource(systemId));
476: * </pre>
477: * </blockquote>
478: * <p/>
479: * If the system identifier is a URL, it must be fully resolved
480: * by the application before it is passed to the parser.</p>
481: * <p/>
482: * <strong>Note</strong>: As a custom SAX parser, this class
483: * ignores the <code>systemId</code> argument of this method
484: * and relies on the proprietary SAX property
485: * {@link #SOURCE_OBJECT_LIST_PROPERTY}) to define the list of
486: * objects to serialize.</p>
487: *
488: * @param systemId the system identifier (URI).
489: * @throws SAXException Any SAX exception, possibly wrapping
490: * another exception.
491: * @see #parse(org.xml.sax.InputSource)
492: */
493: public void parse(String systemId) throws SAXException {
494: this .parse();
495: }
496:
497: /**
498: * Parse an XML document.
499: * <p/>
500: * The application can use this method to instruct the XML
501: * reader to begin parsing an XML document from any valid input
502: * source (a character stream, a byte stream, or a URI).</p>
503: * <p/>
504: * Applications may not invoke this method while a parse is in
505: * progress (they should create a new XMLReader instead for each
506: * nested XML document). Once a parse is complete, an
507: * application may reuse the same XMLReader object, possibly
508: * with a different input source.</p>
509: * <p/>
510: * During the parse, the XMLReader will provide information
511: * about the XML document through the registered event
512: * handlers.</p>
513: * <p/>
514: * This method is synchronous: it will not return until parsing
515: * has ended. If a client application wants to terminate
516: * parsing early, it should throw an exception.</p>
517: * <p/>
518: * <strong>Note</strong>: As a custom SAX parser, this class
519: * ignores the <code>source</code> argument of this method
520: * and relies on the proprietary SAX property
521: * {@link #SOURCE_OBJECT_LIST_PROPERTY}) to define the list of
522: * objects to serialize.</p>
523: *
524: * @param input The input source for the top-level of the
525: * XML document.
526: * @throws SAXException Any SAX exception, possibly wrapping
527: * another exception.
528: * @see org.xml.sax.InputSource
529: * @see #parse(java.lang.String)
530: * @see #setEntityResolver
531: * @see #setDTDHandler
532: * @see #setContentHandler
533: * @see #setErrorHandler
534: */
535: public void parse(InputSource input) throws SAXException {
536: this .parse();
537: }
538:
539: /**
540: * Serializes the Java objects of the configured list into a flow
541: * of SAX events.
542: *
543: * @throws SAXException if the configured object list is invalid
544: * or object serialization failed.
545: */
546: private void parse() throws SAXException {
547: XStream xstream = (XStream) (this .properties
548: .get(CONFIGURED_XSTREAM_PROPERTY));
549: if (xstream == null) {
550: xstream = new XStream();
551: }
552:
553: List source = (List) (this .properties
554: .get(SOURCE_OBJECT_LIST_PROPERTY));
555: if ((source == null) || (source.isEmpty())) {
556: throw new SAXException(
557: "Missing or empty source object list. Setting property \""
558: + SOURCE_OBJECT_LIST_PROPERTY
559: + "\" is mandatory");
560: }
561:
562: try {
563: this .startDocument(true);
564: for (Iterator i = source.iterator(); i.hasNext();) {
565: xstream.marshal(i.next(), this );
566: }
567: this .endDocument(true);
568: } catch (StreamException e) {
569: if (e.getCause() instanceof SAXException) {
570: throw (SAXException) (e.getCause());
571: } else {
572: throw new SAXException(e);
573: }
574: }
575: }
576:
577: //=========================================================================
578: // XStream HierarchicalStreamWriter interface support
579: //=========================================================================
580:
581: private int depth = 0;
582: private List elementStack = new LinkedList();
583: private char[] buffer = new char[128];
584: private boolean startTagInProgress = false;
585: private final AttributesImpl attributeList = new AttributesImpl();
586:
587: public void startNode(String name) {
588: try {
589: if (this .depth != 0) {
590: this .flushStartTag();
591: } else if (includeEnclosingDocument) {
592: this .startDocument(false);
593: }
594: this .elementStack.add(0, escapeXmlName(name));
595:
596: this .startTagInProgress = true;
597: this .depth++;
598: } catch (SAXException e) {
599: throw new StreamException(e);
600: }
601: }
602:
603: public void addAttribute(String name, String value) {
604: if (this .startTagInProgress) {
605: String escapedName = escapeXmlName(name);
606: this .attributeList.addAttribute("", escapedName,
607: escapedName, "CDATA", value);
608: } else {
609: throw new StreamException(new IllegalStateException(
610: "No startElement being processed"));
611: }
612: }
613:
614: public void setValue(String text) {
615: try {
616: this .flushStartTag();
617:
618: int lg = text.length();
619: if (lg > buffer.length) {
620: buffer = new char[lg];
621: }
622: text.getChars(0, lg, buffer, 0);
623:
624: this .contentHandler.characters(buffer, 0, lg);
625: } catch (SAXException e) {
626: throw new StreamException(e);
627: }
628: }
629:
630: public void endNode() {
631: try {
632: this .flushStartTag();
633:
634: String tagName = (String) (this .elementStack.remove(0));
635:
636: this .contentHandler.endElement("", tagName, tagName);
637:
638: this .depth--;
639: if (this .depth == 0 && includeEnclosingDocument) {
640: this .endDocument(false);
641: }
642: } catch (SAXException e) {
643: throw new StreamException(e);
644: }
645: }
646:
647: /**
648: * Fires the SAX startDocument event towards the configured
649: * ContentHandler.
650: *
651: * @param multiObjectMode whether serialization of several
652: * object will be merge into a single
653: * SAX document.
654: * @throws SAXException if thrown by the ContentHandler.
655: */
656: private void startDocument(boolean multiObjectMode)
657: throws SAXException {
658: if (this .depth == 0) {
659: // Notify contentHandler of document start.
660: this .contentHandler.startDocument();
661:
662: if (multiObjectMode) {
663: // Prevent marshalling of each object to fire its own
664: // start/endDocument events.
665: this .depth++;
666: }
667: }
668: }
669:
670: /**
671: * Fires the SAX endDocument event towards the configured
672: * ContentHandler.
673: *
674: * @param multiObjectMode whether serialization of several
675: * object will be merge into a single
676: * SAX document.
677: * @throws SAXException if thrown by the ContentHandler.
678: */
679: private void endDocument(boolean multiObjectMode)
680: throws SAXException {
681: if ((this .depth == 0)
682: || ((this .depth == 1) && (multiObjectMode))) {
683: this .contentHandler.endDocument();
684: this .depth = 0;
685: }
686: }
687:
688: /**
689: * Fires any pending SAX startElement event towards the
690: * configured ContentHandler.
691: *
692: * @throws SAXException if thrown by the ContentHandler.
693: */
694: private void flushStartTag() throws SAXException {
695: if (this .startTagInProgress) {
696: String tagName = (String) (this .elementStack.get(0));
697:
698: this .contentHandler.startElement("", tagName, tagName,
699: this .attributeList);
700: this .attributeList.clear();
701: this .startTagInProgress = false;
702: }
703: }
704:
705: public void flush() {
706: // don't need to do anything
707: }
708:
709: public void close() {
710: // don't need to do anything
711: }
712: }
|