diff --git a/org.springframework.core/core.iml b/org.springframework.core/core.iml
index c38f24baa4b..cb07f548806 100644
--- a/org.springframework.core/core.iml
+++ b/org.springframework.core/core.iml
@@ -1,109 +1,287 @@
-
-XMLReader implementations that use StAX as a basis.
+ *
+ * @author Arjen Poutsma
+ * @see #setContentHandler(org.xml.sax.ContentHandler)
+ * @see #setDTDHandler(org.xml.sax.DTDHandler)
+ * @see #setEntityResolver(org.xml.sax.EntityResolver)
+ * @see #setErrorHandler(org.xml.sax.ErrorHandler)
+ * @since 3.0
+ */
+abstract class AbstractStaxXmlReader extends AbstractXmlReader {
+
+ private static final String NAMESPACES_FEATURE_NAME = "http://xml.org/sax/features/namespaces";
+
+ private static final String NAMESPACE_PREFIXES_FEATURE_NAME = "http://xml.org/sax/features/namespace-prefixes";
+
+ private static final String IS_STANDALONE_FEATURE_NAME = "http://xml.org/sax/features/is-standalone";
+
+ private boolean namespacesFeature = true;
+
+ private boolean namespacePrefixesFeature = false;
+
+ private Boolean isStandalone;
+
+ @Override
+ public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+ if (NAMESPACES_FEATURE_NAME.equals(name)) {
+ return namespacesFeature;
+ }
+ else if (NAMESPACE_PREFIXES_FEATURE_NAME.equals(name)) {
+ return namespacePrefixesFeature;
+ }
+ else if (IS_STANDALONE_FEATURE_NAME.equals(name)) {
+ if (isStandalone != null) {
+ return isStandalone;
+ }
+ else {
+ throw new SAXNotSupportedException("startDocument() callback not completed yet");
+ }
+ }
+ else {
+ return super.getFeature(name);
+ }
+ }
+
+ @Override
+ public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
+ if (NAMESPACES_FEATURE_NAME.equals(name)) {
+ this.namespacesFeature = value;
+ }
+ else if (NAMESPACE_PREFIXES_FEATURE_NAME.equals(name)) {
+ this.namespacePrefixesFeature = value;
+ }
+ else {
+ super.setFeature(name, value);
+ }
+ }
+
+ /** Indicates whether the SAX feature http://xml.org/sax/features/namespaces is turned on. */
+ protected boolean hasNamespacesFeature() {
+ return namespacesFeature;
+ }
+
+ /** Indicates whether the SAX feature http://xml.org/sax/features/namespaces-prefixes is turned on. */
+ protected boolean hasNamespacePrefixesFeature() {
+ return namespacePrefixesFeature;
+ }
+
+ protected void setStandalone(boolean standalone) {
+ isStandalone = (standalone) ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ /**
+ * Parses the StAX XML reader passed at construction-time.
InputSource is not read, but ignored.
+ *
+ * @param ignored is ignored
+ * @throws SAXException A SAX exception, possibly wrapping a XMLStreamException
+ */
+ public final void parse(InputSource ignored) throws SAXException {
+ parse();
+ }
+
+ /**
+ * Parses the StAX XML reader passed at construction-time. Note that the given system identifier
+ * is not read, but ignored.
+ *
+ * @param ignored is ignored
+ * @throws SAXException A SAX exception, possibly wrapping a XMLStreamException
+ */
+ public final void parse(String ignored) throws SAXException {
+ parse();
+ }
+
+ private void parse() throws SAXException {
+ try {
+ parseInternal();
+ }
+ catch (XMLStreamException ex) {
+ Locator locator = null;
+ if (ex.getLocation() != null) {
+ locator = new StaxLocator(ex.getLocation());
+ }
+ SAXParseException saxException = new SAXParseException(ex.getMessage(), locator, ex);
+ if (getErrorHandler() != null) {
+ getErrorHandler().fatalError(saxException);
+ }
+ else {
+ throw saxException;
+ }
+ }
+ }
+
+ /**
+ * Sets the SAX Locator based on the given StAX Location.
+ *
+ * @param location the location
+ * @see ContentHandler#setDocumentLocator(org.xml.sax.Locator)
+ */
+ protected void setLocator(Location location) {
+ if (getContentHandler() != null) {
+ getContentHandler().setDocumentLocator(new StaxLocator(location));
+ }
+ }
+
+ /** Template-method that parses the StAX reader passed at construction-time. */
+ protected abstract void parseInternal() throws SAXException, XMLStreamException;
+
+ /**
+ * Convert a QName to a qualified name, as used by DOM and SAX. The returned string has a format of
+ * prefix:localName if the prefix is set, or just localName if not.
+ *
+ * @param qName the QName
+ * @return the qualified name
+ */
+ protected String toQualifiedName(QName qName) {
+ String prefix = qName.getPrefix();
+ if (!StringUtils.hasLength(prefix)) {
+ return qName.getLocalPart();
+ }
+ else {
+ return prefix + ":" + qName.getLocalPart();
+ }
+ }
+
+ /**
+ * Implementation of the Locator interface that is based on a StAX Location.
+ *
+ * @see Locator
+ * @see Location
+ */
+ private static class StaxLocator implements Locator {
+
+ private Location location;
+
+ protected StaxLocator(Location location) {
+ this.location = location;
+ }
+
+ public String getPublicId() {
+ return location.getPublicId();
+ }
+
+ public String getSystemId() {
+ return location.getSystemId();
+ }
+
+ public int getLineNumber() {
+ return location.getLineNumber();
+ }
+
+ public int getColumnNumber() {
+ return location.getColumnNumber();
+ }
+ }
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/AbstractXmlReader.java b/org.springframework.core/src/main/java/org/springframework/util/xml/AbstractXmlReader.java
new file mode 100644
index 00000000000..2c914b9bea2
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/xml/AbstractXmlReader.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.util.xml;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * Abstract base class for SAX XMLReader implementations. Contains properties as defined in {@link
+ * XMLReader}, and does not recognize any features.
+ *
+ * @author Arjen Poutsma
+ * @see #setContentHandler(org.xml.sax.ContentHandler)
+ * @see #setDTDHandler(org.xml.sax.DTDHandler)
+ * @see #setEntityResolver(org.xml.sax.EntityResolver)
+ * @see #setErrorHandler(org.xml.sax.ErrorHandler)
+ * @since 3.0
+ */
+abstract class AbstractXmlReader implements XMLReader {
+
+ private DTDHandler dtdHandler;
+
+ private ContentHandler contentHandler;
+
+ private EntityResolver entityResolver;
+
+ private ErrorHandler errorHandler;
+
+ private LexicalHandler lexicalHandler;
+
+ public ContentHandler getContentHandler() {
+ return contentHandler;
+ }
+
+ public void setContentHandler(ContentHandler contentHandler) {
+ this.contentHandler = contentHandler;
+ }
+
+ public void setDTDHandler(DTDHandler dtdHandler) {
+ this.dtdHandler = dtdHandler;
+ }
+
+ public DTDHandler getDTDHandler() {
+ return dtdHandler;
+ }
+
+ public EntityResolver getEntityResolver() {
+ return entityResolver;
+ }
+
+ public void setEntityResolver(EntityResolver entityResolver) {
+ this.entityResolver = entityResolver;
+ }
+
+ public ErrorHandler getErrorHandler() {
+ return errorHandler;
+ }
+
+ public void setErrorHandler(ErrorHandler errorHandler) {
+ this.errorHandler = errorHandler;
+ }
+
+ protected LexicalHandler getLexicalHandler() {
+ return lexicalHandler;
+ }
+
+ /**
+ * Throws a SAXNotRecognizedException exception.
+ *
+ * @throws org.xml.sax.SAXNotRecognizedException
+ * always
+ */
+ public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+ throw new SAXNotRecognizedException(name);
+ }
+
+ /**
+ * Throws a SAXNotRecognizedException exception.
+ *
+ * @throws SAXNotRecognizedException always
+ */
+ public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
+ throw new SAXNotRecognizedException(name);
+ }
+
+ /**
+ * Throws a SAXNotRecognizedException exception when the given property does not signify a lexical
+ * handler. The property name for a lexical handler is http://xml.org/sax/properties/lexical-handler.
+ */
+ public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+ if ("http://xml.org/sax/properties/lexical-handler".equals(name)) {
+ return lexicalHandler;
+ }
+ else {
+ throw new SAXNotRecognizedException(name);
+ }
+ }
+
+ /**
+ * Throws a SAXNotRecognizedException exception when the given property does not signify a lexical
+ * handler. The property name for a lexical handler is http://xml.org/sax/properties/lexical-handler.
+ */
+ public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
+ if ("http://xml.org/sax/properties/lexical-handler".equals(name)) {
+ lexicalHandler = (LexicalHandler) value;
+ }
+ else {
+ throw new SAXNotRecognizedException(name);
+ }
+ }
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/StaxEventXmlReader.java b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxEventXmlReader.java
new file mode 100644
index 00000000000..73c3034af85
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxEventXmlReader.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.util.xml;
+
+import java.util.Iterator;
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.Comment;
+import javax.xml.stream.events.DTD;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.EntityDeclaration;
+import javax.xml.stream.events.EntityReference;
+import javax.xml.stream.events.Namespace;
+import javax.xml.stream.events.NotationDeclaration;
+import javax.xml.stream.events.ProcessingInstruction;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * SAX XMLReader that reads from a StAX XMLEventReader. Consumes XMLEvents from
+ * an XMLEventReader, and calls the corresponding methods on the SAX callback interfaces.
+ *
+ * @author Arjen Poutsma
+ * @see XMLEventReader
+ * @see #setContentHandler(org.xml.sax.ContentHandler)
+ * @see #setDTDHandler(org.xml.sax.DTDHandler)
+ * @see #setEntityResolver(org.xml.sax.EntityResolver)
+ * @see #setErrorHandler(org.xml.sax.ErrorHandler)
+ * @since 1.0.0
+ */
+class StaxEventXmlReader extends AbstractStaxXmlReader {
+
+ private final XMLEventReader reader;
+
+ /**
+ * Constructs a new instance of the StaxEventXmlReader that reads from the given
+ * XMLEventReader. The supplied event reader must be in XMLStreamConstants.START_DOCUMENT or
+ * XMLStreamConstants.START_ELEMENT state.
+ *
+ * @param reader the XMLEventReader to read from
+ * @throws IllegalStateException if the reader is not at the start of a document or element
+ */
+ StaxEventXmlReader(XMLEventReader reader) {
+ try {
+ XMLEvent event = reader.peek();
+ if (event == null || !(event.isStartDocument() || event.isStartElement())) {
+ throw new IllegalStateException("XMLEventReader not at start of document or element");
+ }
+ }
+ catch (XMLStreamException ex) {
+ throw new IllegalStateException("Could not read first element: " + ex.getMessage());
+ }
+
+ this.reader = reader;
+ }
+
+ @Override
+ protected void parseInternal() throws SAXException, XMLStreamException {
+ boolean documentStarted = false;
+ boolean documentEnded = false;
+ int elementDepth = 0;
+ while (reader.hasNext() && elementDepth >= 0) {
+ XMLEvent event = reader.nextEvent();
+ if (!event.isStartDocument() && !event.isEndDocument() && !documentStarted) {
+ handleStartDocument();
+ documentStarted = true;
+ }
+ switch (event.getEventType()) {
+ case XMLStreamConstants.START_ELEMENT:
+ elementDepth++;
+ handleStartElement(event.asStartElement());
+ break;
+ case XMLStreamConstants.END_ELEMENT:
+ elementDepth--;
+ if (elementDepth >= 0) {
+ handleEndElement(event.asEndElement());
+ }
+ break;
+ case XMLStreamConstants.PROCESSING_INSTRUCTION:
+ handleProcessingInstruction((ProcessingInstruction) event);
+ break;
+ case XMLStreamConstants.CHARACTERS:
+ case XMLStreamConstants.SPACE:
+ case XMLStreamConstants.CDATA:
+ handleCharacters(event.asCharacters());
+ break;
+ case XMLStreamConstants.START_DOCUMENT:
+ setLocator(event.getLocation());
+ handleStartDocument();
+ documentStarted = true;
+ break;
+ case XMLStreamConstants.END_DOCUMENT:
+ handleEndDocument();
+ documentEnded = true;
+ break;
+ case XMLStreamConstants.NOTATION_DECLARATION:
+ handleNotationDeclaration((NotationDeclaration) event);
+ break;
+ case XMLStreamConstants.ENTITY_DECLARATION:
+ handleEntityDeclaration((EntityDeclaration) event);
+ break;
+ case XMLStreamConstants.COMMENT:
+ handleComment((Comment) event);
+ break;
+ case XMLStreamConstants.DTD:
+ handleDtd((DTD) event);
+ break;
+ case XMLStreamConstants.ENTITY_REFERENCE:
+ handleEntityReference((EntityReference) event);
+ break;
+ }
+ }
+ if (!documentEnded) {
+ handleEndDocument();
+ }
+
+ }
+
+ private void handleStartElement(StartElement startElement) throws SAXException {
+ if (getContentHandler() != null) {
+ QName qName = startElement.getName();
+ if (hasNamespacesFeature()) {
+ for (Iterator i = startElement.getNamespaces(); i.hasNext();) {
+ Namespace namespace = (Namespace) i.next();
+ getContentHandler().startPrefixMapping(namespace.getPrefix(), namespace.getNamespaceURI());
+ }
+ getContentHandler().startElement(qName.getNamespaceURI(), qName.getLocalPart(), toQualifiedName(qName),
+ getAttributes(startElement));
+ }
+ else {
+ getContentHandler().startElement("", "", toQualifiedName(qName), getAttributes(startElement));
+ }
+ }
+ }
+
+ private void handleCharacters(Characters characters) throws SAXException {
+ char[] data = characters.getData().toCharArray();
+ if (getContentHandler() != null && characters.isIgnorableWhiteSpace()) {
+ getContentHandler().ignorableWhitespace(data, 0, data.length);
+ return;
+ }
+ if (characters.isCData() && getLexicalHandler() != null) {
+ getLexicalHandler().startCDATA();
+ }
+ if (getContentHandler() != null) {
+ getContentHandler().characters(data, 0, data.length);
+ }
+ if (characters.isCData() && getLexicalHandler() != null) {
+ getLexicalHandler().endCDATA();
+ }
+ }
+
+ private void handleEndDocument() throws SAXException {
+ if (getContentHandler() != null) {
+ getContentHandler().endDocument();
+ }
+ }
+
+ private void handleEndElement(EndElement endElement) throws SAXException {
+ if (getContentHandler() != null) {
+ QName qName = endElement.getName();
+ if (hasNamespacesFeature()) {
+ getContentHandler().endElement(qName.getNamespaceURI(), qName.getLocalPart(), toQualifiedName(qName));
+ for (Iterator i = endElement.getNamespaces(); i.hasNext();) {
+ Namespace namespace = (Namespace) i.next();
+ getContentHandler().endPrefixMapping(namespace.getPrefix());
+ }
+ }
+ else {
+ getContentHandler().endElement("", "", toQualifiedName(qName));
+ }
+
+ }
+ }
+
+ private void handleNotationDeclaration(NotationDeclaration declaration) throws SAXException {
+ if (getDTDHandler() != null) {
+ getDTDHandler().notationDecl(declaration.getName(), declaration.getPublicId(), declaration.getSystemId());
+ }
+ }
+
+ private void handleEntityDeclaration(EntityDeclaration entityDeclaration) throws SAXException {
+ if (getDTDHandler() != null) {
+ getDTDHandler().unparsedEntityDecl(entityDeclaration.getName(), entityDeclaration.getPublicId(),
+ entityDeclaration.getSystemId(), entityDeclaration.getNotationName());
+ }
+ }
+
+ private void handleProcessingInstruction(ProcessingInstruction pi) throws SAXException {
+ if (getContentHandler() != null) {
+ getContentHandler().processingInstruction(pi.getTarget(), pi.getData());
+ }
+ }
+
+ private void handleStartDocument() throws SAXException {
+ if (getContentHandler() != null) {
+ getContentHandler().startDocument();
+ }
+ }
+
+ private void handleComment(Comment comment) throws SAXException {
+ if (getLexicalHandler() != null) {
+ char[] ch = comment.getText().toCharArray();
+ getLexicalHandler().comment(ch, 0, ch.length);
+ }
+ }
+
+ private void handleDtd(DTD dtd) throws SAXException {
+ if (getLexicalHandler() != null) {
+ javax.xml.stream.Location location = dtd.getLocation();
+ getLexicalHandler().startDTD(null, location.getPublicId(), location.getSystemId());
+ }
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().endDTD();
+ }
+
+ }
+
+ private void handleEntityReference(EntityReference reference) throws SAXException {
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().startEntity(reference.getName());
+ }
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().endEntity(reference.getName());
+ }
+
+ }
+
+ private Attributes getAttributes(StartElement event) {
+ AttributesImpl attributes = new AttributesImpl();
+
+ for (Iterator i = event.getAttributes(); i.hasNext();) {
+ Attribute attribute = (Attribute) i.next();
+ QName qName = attribute.getName();
+ String namespace = qName.getNamespaceURI();
+ if (namespace == null || !hasNamespacesFeature()) {
+ namespace = "";
+ }
+ String type = attribute.getDTDType();
+ if (type == null) {
+ type = "CDATA";
+ }
+ attributes
+ .addAttribute(namespace, qName.getLocalPart(), toQualifiedName(qName), type, attribute.getValue());
+ }
+ if (hasNamespacePrefixesFeature()) {
+ for (Iterator i = event.getNamespaces(); i.hasNext();) {
+ Namespace namespace = (Namespace) i.next();
+ String prefix = namespace.getPrefix();
+ String namespaceUri = namespace.getNamespaceURI();
+ String qName;
+ if (StringUtils.hasLength(prefix)) {
+ qName = "xmlns:" + prefix;
+ }
+ else {
+ qName = "xmlns";
+ }
+ attributes.addAttribute("", "", qName, "CDATA", namespaceUri);
+ }
+ }
+
+ return attributes;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/StaxSource.java b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxSource.java
new file mode 100644
index 00000000000..e69b0172042
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxSource.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.util.xml;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.transform.sax.SAXSource;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+
+/**
+ * Implementation of the Source tagging interface for StAX readers. Can be constructed with a
+ * XMLEventReader or a XMLStreamReader.
+ *
+ * This class is necessary because there is no implementation of Source for StAX Readers in JAXP 1.3.
+ * There is a StAXSource in JAXP 1.4 (JDK 1.6), but this class is kept around for back-ward compatibility
+ * reasons.
+ *
+ *
Even though StaxSource extends from SAXSource, calling the methods of
+ * SAXSource is not supported. In general, the only supported operation on this class is
+ * to use the XMLReader obtained via {@link #getXMLReader()} to parse the input source obtained via {@link
+ * #getInputSource()}. Calling {@link #setXMLReader(XMLReader)} or {@link #setInputSource(InputSource)} will result in
+ * UnsupportedOperationExceptions.
+ *
+ * @author Arjen Poutsma
+ * @see XMLEventReader
+ * @see XMLStreamReader
+ * @see javax.xml.transform.Transformer
+ * @since 3.0
+ */
+public class StaxSource extends SAXSource {
+
+ private XMLEventReader eventReader;
+
+ private XMLStreamReader streamReader;
+
+ /**
+ * Constructs a new instance of the StaxSource with the specified XMLStreamReader. The
+ * supplied stream reader must be in XMLStreamConstants.START_DOCUMENT or
+ * XMLStreamConstants.START_ELEMENT state.
+ *
+ * @param streamReader the XMLStreamReader to read from
+ * @throws IllegalStateException if the reader is not at the start of a document or element
+ */
+ public StaxSource(XMLStreamReader streamReader) {
+ super(new StaxStreamXmlReader(streamReader), new InputSource());
+ this.streamReader = streamReader;
+ }
+
+ /**
+ * Constructs a new instance of the StaxSource with the specified XMLEventReader. The
+ * supplied event reader must be in XMLStreamConstants.START_DOCUMENT or
+ * XMLStreamConstants.START_ELEMENT state.
+ *
+ * @param eventReader the XMLEventReader to read from
+ * @throws IllegalStateException if the reader is not at the start of a document or element
+ */
+ public StaxSource(XMLEventReader eventReader) {
+ super(new StaxEventXmlReader(eventReader), new InputSource());
+ this.eventReader = eventReader;
+ }
+
+ /**
+ * Returns the XMLEventReader used by this StaxSource. If this StaxSource was
+ * created with an XMLStreamReader, the result will be null.
+ *
+ * @return the StAX event reader used by this source
+ * @see StaxSource#StaxSource(javax.xml.stream.XMLEventReader)
+ */
+ public XMLEventReader getXMLEventReader() {
+ return eventReader;
+ }
+
+ /**
+ * Returns the XMLStreamReader used by this StaxSource. If this StaxSource was
+ * created with an XMLEventReader, the result will be null.
+ *
+ * @return the StAX event reader used by this source
+ * @see StaxSource#StaxSource(javax.xml.stream.XMLEventReader)
+ */
+ public XMLStreamReader getXMLStreamReader() {
+ return streamReader;
+ }
+
+ /**
+ * Throws a UnsupportedOperationException.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void setInputSource(InputSource inputSource) {
+ throw new UnsupportedOperationException("setInputSource is not supported");
+ }
+
+ /**
+ * Throws a UnsupportedOperationException.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void setXMLReader(XMLReader reader) {
+ throw new UnsupportedOperationException("setXMLReader is not supported");
+ }
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/StaxStreamXmlReader.java b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxStreamXmlReader.java
new file mode 100644
index 00000000000..2f8a529d9af
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/xml/StaxStreamXmlReader.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.util.xml;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * SAX XMLReader that reads from a StAX XMLStreamReader. Reads from an
+ * XMLStreamReader, and calls the corresponding methods on the SAX callback interfaces.
+ *
+ * @author Arjen Poutsma
+ * @see XMLStreamReader
+ * @see #setContentHandler(org.xml.sax.ContentHandler)
+ * @see #setDTDHandler(org.xml.sax.DTDHandler)
+ * @see #setEntityResolver(org.xml.sax.EntityResolver)
+ * @see #setErrorHandler(org.xml.sax.ErrorHandler)
+ * @since 1.0.0
+ */
+class StaxStreamXmlReader extends AbstractStaxXmlReader {
+
+ private final XMLStreamReader reader;
+
+ /**
+ * Constructs a new instance of the StaxStreamXmlReader that reads from the given
+ * XMLStreamReader. The supplied stream reader must be in XMLStreamConstants.START_DOCUMENT
+ * or XMLStreamConstants.START_ELEMENT state.
+ *
+ * @param reader the XMLEventReader to read from
+ * @throws IllegalStateException if the reader is not at the start of a document or element
+ */
+ StaxStreamXmlReader(XMLStreamReader reader) {
+ int event = reader.getEventType();
+ if (!(event == XMLStreamConstants.START_DOCUMENT || event == XMLStreamConstants.START_ELEMENT)) {
+ throw new IllegalStateException("XMLEventReader not at start of document or element");
+ }
+ this.reader = reader;
+ }
+
+ @Override
+ protected void parseInternal() throws SAXException, XMLStreamException {
+ boolean documentStarted = false;
+ boolean documentEnded = false;
+ int elementDepth = 0;
+ int eventType = reader.getEventType();
+ while (true) {
+ if (eventType != XMLStreamConstants.START_DOCUMENT && eventType != XMLStreamConstants.END_DOCUMENT &&
+ !documentStarted) {
+ handleStartDocument();
+ documentStarted = true;
+ }
+ switch (eventType) {
+ case XMLStreamConstants.START_ELEMENT:
+ elementDepth++;
+ handleStartElement();
+ break;
+ case XMLStreamConstants.END_ELEMENT:
+ elementDepth--;
+ if (elementDepth >= 0) {
+ handleEndElement();
+ }
+ break;
+ case XMLStreamConstants.PROCESSING_INSTRUCTION:
+ handleProcessingInstruction();
+ break;
+ case XMLStreamConstants.CHARACTERS:
+ case XMLStreamConstants.SPACE:
+ case XMLStreamConstants.CDATA:
+ handleCharacters();
+ break;
+ case XMLStreamConstants.START_DOCUMENT:
+ setLocator(reader.getLocation());
+ handleStartDocument();
+ documentStarted = true;
+ break;
+ case XMLStreamConstants.END_DOCUMENT:
+ handleEndDocument();
+ documentEnded = true;
+ break;
+ case XMLStreamConstants.COMMENT:
+ handleComment();
+ break;
+ case XMLStreamConstants.DTD:
+ handleDtd();
+ break;
+ case XMLStreamConstants.ENTITY_REFERENCE:
+ handleEntityReference();
+ break;
+ }
+ if (reader.hasNext() && elementDepth >= 0) {
+ eventType = reader.next();
+ }
+ else {
+ break;
+ }
+ }
+ if (!documentEnded) {
+ handleEndDocument();
+ }
+ }
+
+ private void handleStartDocument() throws SAXException {
+ if (getContentHandler() != null) {
+ getContentHandler().startDocument();
+ if (reader.standaloneSet()) {
+ setStandalone(reader.isStandalone());
+ }
+ }
+ }
+
+ private void handleStartElement() throws SAXException {
+ if (getContentHandler() != null) {
+ QName qName = reader.getName();
+ if (hasNamespacesFeature()) {
+ for (int i = 0; i < reader.getNamespaceCount(); i++) {
+ String prefix = reader.getNamespacePrefix(i);
+ if (prefix == null) {
+ prefix = "";
+ }
+ getContentHandler().startPrefixMapping(prefix, reader.getNamespaceURI(i));
+ }
+ getContentHandler().startElement(qName.getNamespaceURI(), qName.getLocalPart(), toQualifiedName(qName),
+ getAttributes());
+ }
+ else {
+ getContentHandler().startElement("", "", toQualifiedName(qName), getAttributes());
+ }
+ }
+ }
+
+ private void handleEndElement() throws SAXException {
+ if (getContentHandler() != null) {
+ QName qName = reader.getName();
+ if (hasNamespacesFeature()) {
+ getContentHandler().endElement(qName.getNamespaceURI(), qName.getLocalPart(), toQualifiedName(qName));
+ for (int i = 0; i < reader.getNamespaceCount(); i++) {
+ String prefix = reader.getNamespacePrefix(i);
+ if (prefix == null) {
+ prefix = "";
+ }
+ getContentHandler().endPrefixMapping(prefix);
+ }
+ }
+ else {
+ getContentHandler().endElement("", "", toQualifiedName(qName));
+ }
+ }
+ }
+
+ private void handleCharacters() throws SAXException {
+ if (getContentHandler() != null && reader.isWhiteSpace()) {
+ getContentHandler()
+ .ignorableWhitespace(reader.getTextCharacters(), reader.getTextStart(), reader.getTextLength());
+ return;
+ }
+ if (XMLStreamConstants.CDATA == reader.getEventType() && getLexicalHandler() != null) {
+ getLexicalHandler().startCDATA();
+ }
+ if (getContentHandler() != null) {
+ getContentHandler().characters(reader.getTextCharacters(), reader.getTextStart(), reader.getTextLength());
+ }
+ if (XMLStreamConstants.CDATA == reader.getEventType() && getLexicalHandler() != null) {
+ getLexicalHandler().endCDATA();
+ }
+ }
+
+ private void handleComment() throws SAXException {
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().comment(reader.getTextCharacters(), reader.getTextStart(), reader.getTextLength());
+ }
+ }
+
+ private void handleDtd() throws SAXException {
+ if (getLexicalHandler() != null) {
+ javax.xml.stream.Location location = reader.getLocation();
+ getLexicalHandler().startDTD(null, location.getPublicId(), location.getSystemId());
+ }
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().endDTD();
+ }
+ }
+
+ private void handleEntityReference() throws SAXException {
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().startEntity(reader.getLocalName());
+ }
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().endEntity(reader.getLocalName());
+ }
+ }
+
+ private void handleEndDocument() throws SAXException {
+ if (getContentHandler() != null) {
+ getContentHandler().endDocument();
+ }
+ }
+
+ private void handleProcessingInstruction() throws SAXException {
+ if (getContentHandler() != null) {
+ getContentHandler().processingInstruction(reader.getPITarget(), reader.getPIData());
+ }
+ }
+
+ private Attributes getAttributes() {
+ AttributesImpl attributes = new AttributesImpl();
+
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String namespace = reader.getAttributeNamespace(i);
+ if (namespace == null || !hasNamespacesFeature()) {
+ namespace = "";
+ }
+ String type = reader.getAttributeType(i);
+ if (type == null) {
+ type = "CDATA";
+ }
+ attributes.addAttribute(namespace, reader.getAttributeLocalName(i),
+ toQualifiedName(reader.getAttributeName(i)), type, reader.getAttributeValue(i));
+ }
+ if (hasNamespacePrefixesFeature()) {
+ for (int i = 0; i < reader.getNamespaceCount(); i++) {
+ String prefix = reader.getNamespacePrefix(i);
+ String namespaceUri = reader.getNamespaceURI(i);
+ String qName;
+ if (StringUtils.hasLength(prefix)) {
+ qName = "xmlns:" + prefix;
+ }
+ else {
+ qName = "xmlns";
+ }
+ attributes.addAttribute("", "", qName, "CDATA", namespaceUri);
+ }
+ }
+
+ return attributes;
+ }
+
+}
diff --git a/org.springframework.core/src/test/java/org/springframework/util/xml/AbstractStaxXmlReaderTestCase.java b/org.springframework.core/src/test/java/org/springframework/util/xml/AbstractStaxXmlReaderTestCase.java
new file mode 100644
index 00000000000..c96dee49c67
--- /dev/null
+++ b/org.springframework.core/src/test/java/org/springframework/util/xml/AbstractStaxXmlReaderTestCase.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.util.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+
+import org.easymock.AbstractMatcher;
+import org.easymock.MockControl;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+public abstract class AbstractStaxXmlReaderTestCase {
+
+ protected static XMLInputFactory inputFactory;
+
+ private XMLReader standardReader;
+
+ private MockControl contentHandlerControl;
+
+ private ContentHandler contentHandler;
+
+ @Before
+ public void setUp() throws Exception {
+ inputFactory = XMLInputFactory.newInstance();
+ standardReader = XMLReaderFactory.createXMLReader();
+ contentHandlerControl = MockControl.createStrictControl(ContentHandler.class);
+ contentHandlerControl.setDefaultMatcher(new SaxArgumentMatcher());
+ ContentHandler contentHandlerMock = (ContentHandler) contentHandlerControl.getMock();
+ contentHandler = new CopyingContentHandler(contentHandlerMock);
+ standardReader.setContentHandler(contentHandler);
+ }
+
+ private InputStream createTestInputStream() {
+ return getClass().getResourceAsStream("testContentHandler.xml");
+ }
+
+ @Test
+ public void contentHandlerNamespacesNoPrefixes() throws SAXException, IOException, XMLStreamException {
+ standardReader.setFeature("http://xml.org/sax/features/namespaces", true);
+ standardReader.setFeature("http://xml.org/sax/features/namespace-prefixes", false);
+
+ standardReader.parse(new InputSource(createTestInputStream()));
+ contentHandlerControl.replay();
+
+ AbstractStaxXmlReader staxXmlReader = createStaxXmlReader(createTestInputStream());
+ staxXmlReader.setFeature("http://xml.org/sax/features/namespaces", true);
+ staxXmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", false);
+
+ staxXmlReader.setContentHandler(contentHandler);
+ staxXmlReader.parse(new InputSource());
+ contentHandlerControl.verify();
+ }
+
+ @Test
+ public void contentHandlerNamespacesPrefixes() throws SAXException, IOException, XMLStreamException {
+ standardReader.setFeature("http://xml.org/sax/features/namespaces", true);
+ standardReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
+
+ standardReader.parse(new InputSource(createTestInputStream()));
+ contentHandlerControl.replay();
+
+ AbstractStaxXmlReader staxXmlReader = createStaxXmlReader(createTestInputStream());
+ staxXmlReader.setFeature("http://xml.org/sax/features/namespaces", true);
+ staxXmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
+
+ staxXmlReader.setContentHandler(contentHandler);
+ staxXmlReader.parse(new InputSource());
+ contentHandlerControl.verify();
+ }
+
+ @Test
+ public void contentHandlerNoNamespacesPrefixes() throws SAXException, IOException, XMLStreamException {
+ standardReader.setFeature("http://xml.org/sax/features/namespaces", false);
+ standardReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
+
+ standardReader.parse(new InputSource(createTestInputStream()));
+ contentHandlerControl.replay();
+
+ AbstractStaxXmlReader staxXmlReader = createStaxXmlReader(createTestInputStream());
+ staxXmlReader.setFeature("http://xml.org/sax/features/namespaces", false);
+ staxXmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
+
+ staxXmlReader.setContentHandler(contentHandler);
+ staxXmlReader.parse(new InputSource());
+ contentHandlerControl.verify();
+ }
+
+ @Test
+ @Ignore
+ public void lexicalHandler() throws SAXException, IOException, XMLStreamException {
+ MockControl lexicalHandlerControl = MockControl.createControl(LexicalHandler.class);
+ lexicalHandlerControl.setDefaultMatcher(new SaxArgumentMatcher());
+ LexicalHandler lexicalHandlerMock = (LexicalHandler) lexicalHandlerControl.getMock();
+ LexicalHandler lexicalHandler = new CopyingLexicalHandler(lexicalHandlerMock);
+
+ Resource testLexicalHandlerXml = new ClassPathResource("testLexicalHandler.xml", getClass());
+
+ standardReader.setContentHandler(null);
+ standardReader.setProperty("http://xml.org/sax/properties/lexical-handler", lexicalHandler);
+ standardReader.parse(new InputSource(createTestInputStream()));
+ lexicalHandlerControl.replay();
+
+ inputFactory.setProperty("javax.xml.stream.isCoalescing", Boolean.FALSE);
+ inputFactory.setProperty("http://java.sun.com/xml/stream/properties/report-cdata-event", Boolean.TRUE);
+ inputFactory.setProperty("javax.xml.stream.isReplacingEntityReferences", Boolean.FALSE);
+ inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", Boolean.FALSE);
+
+ AbstractStaxXmlReader staxXmlReader = createStaxXmlReader(testLexicalHandlerXml.getInputStream());
+
+ staxXmlReader.setProperty("http://xml.org/sax/properties/lexical-handler", lexicalHandler);
+ staxXmlReader.parse(new InputSource());
+ lexicalHandlerControl.verify();
+ }
+
+ protected abstract AbstractStaxXmlReader createStaxXmlReader(InputStream inputStream) throws XMLStreamException;
+
+ /** Easymock ArgumentMatcher implementation that matches SAX arguments. */
+ protected static class SaxArgumentMatcher extends AbstractMatcher {
+
+ @Override
+ public boolean matches(Object[] expected, Object[] actual) {
+ if (expected == actual) {
+ return true;
+ }
+ if (expected == null || actual == null) {
+ return false;
+ }
+ if (expected.length != actual.length) {
+ throw new IllegalArgumentException("Expected and actual arguments must have the same size");
+ }
+ if (expected.length == 3 && expected[0] instanceof char[] && expected[1] instanceof Integer &&
+ expected[2] instanceof Integer) {
+ // handling of the character(char[], int, int) methods
+ String expectedString = new String((char[]) expected[0], (Integer) expected[1], (Integer) expected[2]);
+ String actualString = new String((char[]) actual[0], (Integer) actual[1], (Integer) actual[2]);
+ return expectedString.equals(actualString);
+ }
+ else if (expected.length == 1 && (expected[0] instanceof Locator)) {
+ return true;
+ }
+ else {
+ return super.matches(expected, actual);
+ }
+ }
+
+ @Override
+ protected boolean argumentMatches(Object expected, Object actual) {
+ if (expected instanceof char[]) {
+ return Arrays.equals((char[]) expected, (char[]) actual);
+ }
+ else if (expected instanceof Attributes) {
+ Attributes expectedAttributes = (Attributes) expected;
+ Attributes actualAttributes = (Attributes) actual;
+ if (expectedAttributes.getLength() != actualAttributes.getLength()) {
+ return false;
+ }
+ for (int i = 0; i < expectedAttributes.getLength(); i++) {
+ boolean found = false;
+ for (int j = 0; j < actualAttributes.getLength(); j++) {
+ if (expectedAttributes.getURI(i).equals(actualAttributes.getURI(j)) &&
+ expectedAttributes.getQName(i).equals(actualAttributes.getQName(j)) &&
+ expectedAttributes.getType(i).equals(actualAttributes.getType(j)) &&
+ expectedAttributes.getValue(i).equals(actualAttributes.getValue(j))) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ return true;
+ }
+ else {
+ return super.argumentMatches(expected, actual);
+ }
+ }
+
+ @Override
+ public String toString(Object[] arguments) {
+ if (arguments != null && arguments.length == 3 && arguments[0] instanceof char[] &&
+ arguments[1] instanceof Integer && arguments[2] instanceof Integer) {
+ return new String((char[]) arguments[0], (Integer) arguments[1], (Integer) arguments[2]);
+ }
+ else {
+ return super.toString(arguments);
+ }
+ }
+
+ @Override
+ protected String argumentToString(Object argument) {
+ if (argument instanceof char[]) {
+ char[] array = (char[]) argument;
+ StringBuilder buffer = new StringBuilder();
+ for (char anArray : array) {
+ buffer.append(anArray);
+ }
+ return buffer.toString();
+ }
+ else if (argument instanceof Attributes) {
+ Attributes attributes = (Attributes) argument;
+ StringBuilder buffer = new StringBuilder("[");
+ for (int i = 0; i < attributes.getLength(); i++) {
+ if (attributes.getURI(i).length() != 0) {
+ buffer.append('{');
+ buffer.append(attributes.getURI(i));
+ buffer.append('}');
+ }
+ if (attributes.getQName(i).length() != 0) {
+ buffer.append(attributes.getQName(i));
+ }
+ buffer.append('=');
+ buffer.append(attributes.getValue(i));
+ if (i < attributes.getLength() - 1) {
+ buffer.append(", ");
+ }
+ }
+ buffer.append(']');
+ return buffer.toString();
+ }
+ else if (argument instanceof Locator) {
+ Locator locator = (Locator) argument;
+ StringBuilder buffer = new StringBuilder("[");
+ buffer.append(locator.getLineNumber());
+ buffer.append(',');
+ buffer.append(locator.getColumnNumber());
+ buffer.append(']');
+ return buffer.toString();
+ }
+ else {
+ return super.argumentToString(argument);
+ }
+ }
+ }
+
+ private static class CopyingContentHandler implements ContentHandler {
+
+ private final ContentHandler wrappee;
+
+ private CopyingContentHandler(ContentHandler wrappee) {
+ this.wrappee = wrappee;
+ }
+
+ public void setDocumentLocator(Locator locator) {
+ wrappee.setDocumentLocator(locator);
+ }
+
+ public void startDocument() throws SAXException {
+ wrappee.startDocument();
+ }
+
+ public void endDocument() throws SAXException {
+ wrappee.endDocument();
+ }
+
+ public void startPrefixMapping(String prefix, String uri) throws SAXException {
+ wrappee.startPrefixMapping(prefix, uri);
+ }
+
+ public void endPrefixMapping(String prefix) throws SAXException {
+ wrappee.endPrefixMapping(prefix);
+ }
+
+ public void startElement(String uri, String localName, String qName, Attributes attributes)
+ throws SAXException {
+ wrappee.startElement(uri, localName, qName, new AttributesImpl(attributes));
+ }
+
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ wrappee.endElement(uri, localName, qName);
+ }
+
+ public void characters(char ch[], int start, int length) throws SAXException {
+ wrappee.characters(copy(ch), start, length);
+ }
+
+ public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
+ }
+
+ public void processingInstruction(String target, String data) throws SAXException {
+ wrappee.processingInstruction(target, data);
+ }
+
+ public void skippedEntity(String name) throws SAXException {
+ wrappee.skippedEntity(name);
+ }
+ }
+
+ private static class CopyingLexicalHandler implements LexicalHandler {
+
+ private final LexicalHandler wrappee;
+
+ private CopyingLexicalHandler(LexicalHandler wrappee) {
+ this.wrappee = wrappee;
+ }
+
+ public void startDTD(String name, String publicId, String systemId) throws SAXException {
+ wrappee.startDTD("element", publicId, systemId);
+ }
+
+ public void endDTD() throws SAXException {
+ wrappee.endDTD();
+ }
+
+ public void startEntity(String name) throws SAXException {
+ wrappee.startEntity(name);
+ }
+
+ public void endEntity(String name) throws SAXException {
+ wrappee.endEntity(name);
+ }
+
+ public void startCDATA() throws SAXException {
+ wrappee.startCDATA();
+ }
+
+ public void endCDATA() throws SAXException {
+ wrappee.endCDATA();
+ }
+
+ public void comment(char ch[], int start, int length) throws SAXException {
+ wrappee.comment(copy(ch), start, length);
+ }
+ }
+
+ private static char[] copy(char[] ch) {
+ char[] copy = new char[ch.length];
+ System.arraycopy(ch, 0, copy, 0, ch.length);
+ return copy;
+ }
+
+}
diff --git a/org.springframework.core/src/test/java/org/springframework/util/xml/StaxEventXmlReaderTest.java b/org.springframework.core/src/test/java/org/springframework/util/xml/StaxEventXmlReaderTest.java
new file mode 100644
index 00000000000..ecc9d171e9c
--- /dev/null
+++ b/org.springframework.core/src/test/java/org/springframework/util/xml/StaxEventXmlReaderTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.util.xml;
+
+import java.io.InputStream;
+import java.io.StringReader;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+
+import org.easymock.MockControl;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.helpers.AttributesImpl;
+
+public class StaxEventXmlReaderTest extends AbstractStaxXmlReaderTestCase {
+
+ public static final String CONTENT = "