cucumber.deps.com.thoughtworks.xstream.io.xml.SaxWriter Maven / Gradle / Ivy
/*
* Copyright (C) 2004, 2005, 2006 Joe Walnes.
* Copyright (C) 2006, 2007, 2009, 2011 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
* Created on 14. August 2004 by Joe Walnes
*/
package com.thoughtworks.xstream.io.xml;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.StreamException;
import com.thoughtworks.xstream.io.naming.NameCoder;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* A SAX {@link org.xml.sax.XMLReader parser} that acts as an XStream
* {@link com.thoughtworks.xstream.io.HierarchicalStreamWriter} to enable direct generation of a
* SAX event flow from the XStream serialization of a list of list of Java objects. As a
* custom SAX parser, this class ignores the arguments of the two standard parse methods ({@link #parse(java.lang.String)}
* and {@link #parse(org.xml.sax.InputSource)}) but relies on a proprietary SAX property
* {@link #SOURCE_OBJECT_LIST_PROPERTY} to define the list of objects to serialize.
*
* Configuration of this SAX parser is achieved through the standard
* {@link #setProperty SAX property mechanism}. While specific setter methods require direct
* access to the parser instance, SAX properties support configuration settings to be propagated
* through a chain of {@link org.xml.sax.XMLFilter filters} down to the underlying parser
* object.
*
* This mechanism shall be used to configure the
* {@link #SOURCE_OBJECT_LIST_PROPERTY objects to be serialized} as well as the
* {@link #CONFIGURED_XSTREAM_PROPERTY XStream facade}.
*
*
* @author Laurent Bihanic
*/
public final class SaxWriter extends AbstractXmlWriter implements XMLReader {
/**
* The {@link #setProperty SAX property} to configure the XStream
* facade to be used for object serialization. If the property
* is not set, a new XStream facade will be allocated for each
* parse.
*/
public final static String CONFIGURED_XSTREAM_PROPERTY =
"http://com.thoughtworks.xstream/sax/property/configured-xstream";
/**
* The {@link #setProperty SAX property} to configure the list of
* Java objects to serialize. Setting this property prior
* invoking one of the parse() methods is mandatory.
*
* @see #parse(java.lang.String)
* @see #parse(org.xml.sax.InputSource)
*/
public final static String SOURCE_OBJECT_LIST_PROPERTY =
"http://com.thoughtworks.xstream/sax/property/source-object-list";
//=========================================================================
// SAX XMLReader interface support
//=========================================================================
/**
* The SAX EntityResolver associated to this XMLReader.
*/
private EntityResolver entityResolver = null;
/**
* The SAX DTDHandler associated to this XMLReader.
*/
private DTDHandler dtdHandler = null;
/**
* The SAX ContentHandler associated to this XMLReader.
*/
private ContentHandler contentHandler = null;
/**
* The SAX ErrorHandler associated to this XMLReader.
*/
private ErrorHandler errorHandler = null;
/**
* The SAX features defined for this XMLReader.
*
* This class does not define any feature (yet) and ignores
* the SAX mandatory feature. Thus, this member is present
* only to support the mandatory feature setting and retrieval
* logic defined by SAX.
*/
private Map features = new HashMap();
/**
* The SAX properties defined for this XMLReader.
*/
private final Map properties = new HashMap();
private final boolean includeEnclosingDocument;
/**
* @since 1.4
*/
public SaxWriter(NameCoder nameCoder)
{
this(true, nameCoder);
}
/**
* @since 1.4
*/
public SaxWriter(boolean includeEnclosingDocument, NameCoder nameCoder)
{
super(nameCoder);
this.includeEnclosingDocument = includeEnclosingDocument;
}
/**
* @deprecated As of 1.4 use {@link SaxWriter#SaxWriter(NameCoder)} instead.
*/
public SaxWriter(XmlFriendlyReplacer replacer)
{
this(true, replacer);
}
/**
* @deprecated As of 1.4 use {@link SaxWriter#SaxWriter(boolean, NameCoder)} instead.
*/
public SaxWriter(boolean includeEnclosingDocument, XmlFriendlyReplacer replacer)
{
this(includeEnclosingDocument, (NameCoder)replacer);
}
public SaxWriter(boolean includeEnclosingDocument) {
this(includeEnclosingDocument, new XmlFriendlyNameCoder());
}
public SaxWriter() {
this(true);
}
//-------------------------------------------------------------------------
// Configuration
//-------------------------------------------------------------------------
/**
* Sets the state of a feature.
*
* The feature name is any fully-qualified URI.
*
* All XMLReaders are required to support setting
* http://xml.org/sax/features/namespaces
to
* true
and
* http://xml.org/sax/features/namespace-prefixes
to
* false
.
*
* Some feature values may be immutable or mutable only
* in specific contexts, such as before, during, or after
* a parse.
*
* Note: This implementation only supports the two
* mandatory SAX features.
*
* @param name the feature name, which is a fully-qualified URI.
* @param value the requested state of the feature (true or false).
* @throws SAXNotRecognizedException when the XMLReader does not
* recognize the feature name.
* @see #getFeature
*/
public void setFeature(String name, boolean value)
throws SAXNotRecognizedException {
if ((name.equals("http://xml.org/sax/features/namespaces")) ||
(name.equals("http://xml.org/sax/features/namespace-prefixes"))) {
this.features.put(name, value ? Boolean.TRUE : Boolean.FALSE); // JDK 1.3 friendly
} else {
throw new SAXNotRecognizedException(name);
}
}
/**
* Looks up the value of a feature.
*
* The feature name is any fully-qualified URI. It is
* possible for an XMLReader to recognize a feature name but
* to be unable to return its value; this is especially true
* in the case of an adapter for a SAX1 Parser, which has
* no way of knowing whether the underlying parser is
* performing validation or expanding external entities.
*
* All XMLReaders are required to recognize the
* http://xml.org/sax/features/namespaces
and the
* http://xml.org/sax/features/namespace-prefixes
feature
* names.
*
* Some feature values may be available only in specific
* contexts, such as before, during, or after a parse.
*
* Implementors are free (and encouraged) to invent their own
* features, using names built on their own URIs.
*
* @param name the feature name, which is a fully-qualified URI.
* @return the current state of the feature (true or false).
* @throws SAXNotRecognizedException when the XMLReader does not
* recognize the feature name.
* @see #setFeature
*/
public boolean getFeature(String name)
throws SAXNotRecognizedException {
if ((name.equals("http://xml.org/sax/features/namespaces")) ||
(name.equals("http://xml.org/sax/features/namespace-prefixes"))) {
Boolean value = (Boolean) (this.features.get(name));
if (value == null) {
value = Boolean.FALSE;
}
return value.booleanValue();
} else {
throw new SAXNotRecognizedException(name);
}
}
/**
* Sets the value of a property.
*
* The property name is any fully-qualified URI. It is
* possible for an XMLReader to recognize a property name but
* to be unable to set its value.
*
* XMLReaders are not required to recognize setting any
* specific property names, though a core set is provided with
* SAX2.
*
* Some property values may be immutable or mutable only
* in specific contexts, such as before, during, or after
* a parse.
*
* This method is also the standard mechanism for setting
* extended handlers.
*
* Note: This implementation only supports two
* (proprietary) properties: {@link #CONFIGURED_XSTREAM_PROPERTY}
* and {@link #SOURCE_OBJECT_LIST_PROPERTY}.
*
* @param name the property name, which is a fully-qualified URI.
* @param value the requested value for the property.
* @throws SAXNotRecognizedException when the XMLReader does not
* recognize the property name.
* @throws SAXNotSupportedException when the XMLReader recognizes
* the property name but cannot set
* the requested value.
* @see #getProperty
*/
public void setProperty(String name, Object value)
throws SAXNotRecognizedException,
SAXNotSupportedException {
if (name.equals(CONFIGURED_XSTREAM_PROPERTY)) {
if (!(value instanceof XStream)) {
throw new SAXNotSupportedException("Value for property \"" +
CONFIGURED_XSTREAM_PROPERTY +
"\" must be a non-null XStream object");
}
} else if (name.equals(SOURCE_OBJECT_LIST_PROPERTY)) {
if (value instanceof List) {
List list = (List) value;
if (list.isEmpty()) {
throw new SAXNotSupportedException("Value for property \"" +
SOURCE_OBJECT_LIST_PROPERTY +
"\" shall not be an empty list");
} else {
// Perform a copy of the list to prevent the application to
// modify its content while the parse is being performed.
value = Collections.unmodifiableList(new ArrayList(list));
}
} else {
throw new SAXNotSupportedException("Value for property \"" +
SOURCE_OBJECT_LIST_PROPERTY +
"\" must be a non-null List object");
}
} else {
throw new SAXNotRecognizedException(name);
}
this.properties.put(name, value);
}
/**
* Looks up the value of a property.
*
* The property name is any fully-qualified URI. It is
* possible for an XMLReader to recognize a property name but
* to be unable to return its state.
*
* XMLReaders are not required to recognize any specific
* property names, though an initial core set is documented for
* SAX2.
*
* Some property values may be available only in specific
* contexts, such as before, during, or after a parse.
*
* Implementors are free (and encouraged) to invent their own properties,
* using names built on their own URIs.
*
* @param name the property name, which is a fully-qualified URI.
* @return the current value of the property.
* @throws SAXNotRecognizedException when the XMLReader does not
* recognize the property name.
* @see #getProperty
*/
public Object getProperty(String name)
throws SAXNotRecognizedException {
if ((name.equals(CONFIGURED_XSTREAM_PROPERTY)) ||
(name.equals(SOURCE_OBJECT_LIST_PROPERTY))) {
return this.properties.get(name);
} else {
throw new SAXNotRecognizedException(name);
}
}
//---------------------------------------------------------------------
// Event handlers
//---------------------------------------------------------------------
/**
* Allows an application to register an entity resolver.
*
* If the application does not register an entity resolver,
* the XMLReader will perform its own default resolution.
*
* Applications may register a new or different resolver in the
* middle of a parse, and the SAX parser must begin using the new
* resolver immediately.
*
* @param resolver the entity resolver.
* @throws NullPointerException if the resolver argument is
* null
.
* @see #getEntityResolver
*/
public void setEntityResolver(EntityResolver resolver) {
if (resolver == null) {
throw new NullPointerException("resolver");
}
this.entityResolver = resolver;
return;
}
/**
* Returns the current entity resolver.
*
* @return the current entity resolver, or null
if none
* has been registered.
* @see #setEntityResolver
*/
public EntityResolver getEntityResolver() {
return this.entityResolver;
}
/**
* Allows an application to register a DTD event handler.
*
* If the application does not register a DTD handler, all DTD
* events reported by the SAX parser will be silently ignored.
*
* Applications may register a new or different handler in the
* middle of a parse, and the SAX parser must begin using the new
* handler immediately.
*
* @param handler the DTD handler.
* @throws NullPointerException if the handler argument is
* null
.
* @see #getDTDHandler
*/
public void setDTDHandler(DTDHandler handler) {
if (handler == null) {
throw new NullPointerException("handler");
}
this.dtdHandler = handler;
return;
}
/**
* Returns the current DTD handler.
*
* @return the current DTD handler, or null
if none
* has been registered.
* @see #setDTDHandler
*/
public DTDHandler getDTDHandler() {
return this.dtdHandler;
}
/**
* Allows an application to register a content event handler.
*
* If the application does not register a content handler, all
* content events reported by the SAX parser will be silently
* ignored.
*
* Applications may register a new or different handler in the
* middle of a parse, and the SAX parser must begin using the new
* handler immediately.
*
* @param handler the content handler.
* @throws NullPointerException if the handler argument is
* null
.
* @see #getContentHandler
*/
public void setContentHandler(ContentHandler handler) {
if (handler == null) {
throw new NullPointerException("handler");
}
this.contentHandler = handler;
return;
}
/**
* Returns the current content handler.
*
* @return the current content handler, or null
if none
* has been registered.
* @see #setContentHandler
*/
public ContentHandler getContentHandler() {
return this.contentHandler;
}
/**
* Allows an application to register an error event handler.
*
* If the application does not register an error handler, all
* error events reported by the SAX parser will be silently
* ignored; however, normal processing may not continue. It is
* highly recommended that all SAX applications implement an
* error handler to avoid unexpected bugs.
*
* Applications may register a new or different handler in the
* middle of a parse, and the SAX parser must begin using the new
* handler immediately.
*
* @param handler the error handler.
* @throws NullPointerException if the handler argument is
* null
.
* @see #getErrorHandler
*/
public void setErrorHandler(ErrorHandler handler) {
if (handler == null) {
throw new NullPointerException("handler");
}
this.errorHandler = handler;
return;
}
/**
* Returns the current error handler.
*
* @return the current error handler, or null
if none
* has been registered.
* @see #setErrorHandler
*/
public ErrorHandler getErrorHandler() {
return this.errorHandler;
}
//---------------------------------------------------------------------
// Parsing
//---------------------------------------------------------------------
/**
* Parses an XML document from a system identifier (URI).
*
* This method is a shortcut for the common case of reading a
* document from a system identifier. It is the exact
* equivalent of the following:
*
*
* parse(new InputSource(systemId));
*
*
*
* If the system identifier is a URL, it must be fully resolved
* by the application before it is passed to the parser.
*
* Note: As a custom SAX parser, this class
* ignores the systemId
argument of this method
* and relies on the proprietary SAX property
* {@link #SOURCE_OBJECT_LIST_PROPERTY}) to define the list of
* objects to serialize.
*
* @param systemId the system identifier (URI).
* @throws SAXException Any SAX exception, possibly wrapping
* another exception.
* @see #parse(org.xml.sax.InputSource)
*/
public void parse(String systemId) throws SAXException {
this.parse();
}
/**
* Parse an XML document.
*
* The application can use this method to instruct the XML
* reader to begin parsing an XML document from any valid input
* source (a character stream, a byte stream, or a URI).
*
* Applications may not invoke this method while a parse is in
* progress (they should create a new XMLReader instead for each
* nested XML document). Once a parse is complete, an
* application may reuse the same XMLReader object, possibly
* with a different input source.
*
* During the parse, the XMLReader will provide information
* about the XML document through the registered event
* handlers.
*
* This method is synchronous: it will not return until parsing
* has ended. If a client application wants to terminate
* parsing early, it should throw an exception.
*
* Note: As a custom SAX parser, this class
* ignores the source
argument of this method
* and relies on the proprietary SAX property
* {@link #SOURCE_OBJECT_LIST_PROPERTY}) to define the list of
* objects to serialize.
*
* @param input The input source for the top-level of the
* XML document.
* @throws SAXException Any SAX exception, possibly wrapping
* another exception.
* @see org.xml.sax.InputSource
* @see #parse(java.lang.String)
* @see #setEntityResolver
* @see #setDTDHandler
* @see #setContentHandler
* @see #setErrorHandler
*/
public void parse(InputSource input) throws SAXException {
this.parse();
}
/**
* Serializes the Java objects of the configured list into a flow
* of SAX events.
*
* @throws SAXException if the configured object list is invalid
* or object serialization failed.
*/
private void parse() throws SAXException {
XStream xstream = (XStream) (this.properties.get(CONFIGURED_XSTREAM_PROPERTY));
if (xstream == null) {
xstream = new XStream();
}
List source = (List) (this.properties.get(SOURCE_OBJECT_LIST_PROPERTY));
if ((source == null) || (source.isEmpty())) {
throw new SAXException("Missing or empty source object list. Setting property \"" +
SOURCE_OBJECT_LIST_PROPERTY + "\" is mandatory");
}
try {
this.startDocument(true);
for (Iterator i = source.iterator(); i.hasNext();) {
xstream.marshal(i.next(), this);
}
this.endDocument(true);
} catch (StreamException e) {
if (e.getCause() instanceof SAXException) {
throw (SAXException) (e.getCause());
} else {
throw new SAXException(e);
}
}
}
//=========================================================================
// XStream HierarchicalStreamWriter interface support
//=========================================================================
private int depth = 0;
private List elementStack = new LinkedList();
private char[] buffer = new char[128];
private boolean startTagInProgress = false;
private final AttributesImpl attributeList = new AttributesImpl();
public void startNode(String name) {
try {
if (this.depth != 0) {
this.flushStartTag();
} else if (includeEnclosingDocument) {
this.startDocument(false);
}
this.elementStack.add(0, escapeXmlName(name));
this.startTagInProgress = true;
this.depth++;
} catch (SAXException e) {
throw new StreamException(e);
}
}
public void addAttribute(String name, String value) {
if (this.startTagInProgress) {
String escapedName = escapeXmlName(name);
this.attributeList.addAttribute("", escapedName, escapedName, "CDATA", value);
} else {
throw new StreamException(new IllegalStateException("No startElement being processed"));
}
}
public void setValue(String text) {
try {
this.flushStartTag();
int lg = text.length();
if (lg > buffer.length) {
buffer = new char[lg];
}
text.getChars(0, lg, buffer, 0);
this.contentHandler.characters(buffer, 0, lg);
} catch (SAXException e) {
throw new StreamException(e);
}
}
public void endNode() {
try {
this.flushStartTag();
String tagName = (String) (this.elementStack.remove(0));
this.contentHandler.endElement("", tagName, tagName);
this.depth--;
if (this.depth == 0 && includeEnclosingDocument) {
this.endDocument(false);
}
} catch (SAXException e) {
throw new StreamException(e);
}
}
/**
* Fires the SAX startDocument event towards the configured
* ContentHandler.
*
* @param multiObjectMode whether serialization of several
* object will be merge into a single
* SAX document.
* @throws SAXException if thrown by the ContentHandler.
*/
private void startDocument(boolean multiObjectMode) throws SAXException {
if (this.depth == 0) {
// Notify contentHandler of document start.
this.contentHandler.startDocument();
if (multiObjectMode) {
// Prevent marshalling of each object to fire its own
// start/endDocument events.
this.depth++;
}
}
}
/**
* Fires the SAX endDocument event towards the configured
* ContentHandler.
*
* @param multiObjectMode whether serialization of several
* object will be merge into a single
* SAX document.
* @throws SAXException if thrown by the ContentHandler.
*/
private void endDocument(boolean multiObjectMode) throws SAXException {
if ((this.depth == 0) || ((this.depth == 1) && (multiObjectMode))) {
this.contentHandler.endDocument();
this.depth = 0;
}
}
/**
* Fires any pending SAX startElement event towards the
* configured ContentHandler.
*
* @throws SAXException if thrown by the ContentHandler.
*/
private void flushStartTag() throws SAXException {
if (this.startTagInProgress) {
String tagName = (String) (this.elementStack.get(0));
this.contentHandler.startElement("", tagName,
tagName, this.attributeList);
this.attributeList.clear();
this.startTagInProgress = false;
}
}
public void flush() {
// don't need to do anything
}
public void close() {
// don't need to do anything
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy