From b5a358019f799d92b73f67eacb411ec966333b21 Mon Sep 17 00:00:00 2001
From: Sam Brannen <104798+sbrannen@users.noreply.github.com>
Date: Fri, 4 Sep 2026 14:50:16 +0200
Subject: [PATCH 1/3] Polishing
---
.../util/xml/XMLEventStreamReader.java | 8 +++++---
.../util/xml/XMLEventStreamReaderTests.java | 16 +++++++++-------
2 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java
index e554dbd6ebd..3e8725e3d76 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java
@@ -34,9 +34,11 @@ import javax.xml.stream.events.XMLEvent;
import org.jspecify.annotations.Nullable;
/**
- * Implementation of the {@link javax.xml.stream.XMLStreamReader} interface that wraps a
- * {@link XMLEventReader}. Useful because the StAX {@link javax.xml.stream.XMLInputFactory}
- * allows one to create an event reader from a stream reader, but not vice-versa.
+ * Implementation of the {@link javax.xml.stream.XMLStreamReader} interface that wraps an
+ * {@link XMLEventReader}.
+ *
+ *
Useful because the StAX {@link javax.xml.stream.XMLInputFactory} allows one
+ * to create an event reader from a stream reader, but not vice-versa.
*
* @author Arjen Poutsma
* @since 3.0
diff --git a/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java b/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java
index 40ef3b6410d..9a261226c86 100644
--- a/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java
+++ b/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java
@@ -26,7 +26,6 @@ import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamResult;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Node;
import org.xmlunit.util.Predicate;
@@ -35,21 +34,24 @@ import org.springframework.core.testfixture.xml.XmlContent;
import static org.assertj.core.api.Assertions.assertThat;
+/**
+ * Tests for {@link XMLEventStreamReader}.
+ */
class XMLEventStreamReaderTests {
private static final String XML =
- "content"
- ;
+ "content";
- private XMLEventStreamReader streamReader;
+ private final XMLEventStreamReader streamReader;
- @BeforeEach
- void createStreamReader() throws Exception {
+
+ XMLEventStreamReaderTests() throws Exception {
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
XMLEventReader eventReader = inputFactory.createXMLEventReader(new StringReader(XML));
- streamReader = new XMLEventStreamReader(eventReader);
+ this.streamReader = new XMLEventStreamReader(eventReader);
}
+
@Test
void readAll() throws Exception {
while (streamReader.hasNext()) {
From 2b276311ebe715354a10610c102a09e4c9be89a7 Mon Sep 17 00:00:00 2001
From: junhyeong9812
Date: Sat, 13 Jun 2026 19:33:37 +0900
Subject: [PATCH 2/3] Honor sourceStart offset in
AbstractXMLStreamReader#getTextCharacters
Prior to this commit, AbstractXMLStreamReader.getTextCharacters(int
sourceStart, char[], int, int) capped the copy length with
Math.min(length, source.length), ignoring sourceStart. When sourceStart
> 0 and sourceStart + length exceeds the text length, System.arraycopy
read past the end of the source array and threw
ArrayIndexOutOfBoundsException, contrary to the
XMLStreamReader#getTextCharacters contract (copy up to length
characters starting at sourceStart and return the number copied).
To address that, this commit caps the length by the number of
characters remaining from sourceStart.
Closes gh-36914
Signed-off-by: junhyeong9812
---
.../util/xml/AbstractXMLStreamReader.java | 2 +-
.../util/xml/XMLEventStreamReaderTests.java | 20 +++++++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java
index 1c92c458fcd..1bba07dc5f3 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java
@@ -190,7 +190,7 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader {
@Override
public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length) {
char[] source = getTextCharacters();
- length = Math.min(length, source.length);
+ length = Math.min(length, source.length - sourceStart);
System.arraycopy(source, sourceStart, target, targetStart, length);
return length;
}
diff --git a/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java b/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java
index 9a261226c86..0a517e7abea 100644
--- a/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java
+++ b/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java
@@ -21,6 +21,7 @@ import java.io.StringWriter;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stax.StAXSource;
@@ -70,4 +71,23 @@ class XMLEventStreamReaderTests {
assertThat(XmlContent.from(writer)).isSimilarTo(XML, nodeFilter);
}
+ @Test // gh-36914
+ void getTextCharactersHonorsSourceStart() throws Exception {
+ char[] target = new char[10];
+
+ advanceToCharacters();
+
+ // text node is "content" (7 chars); copy from index 4 with an oversized buffer
+ // getTextCharacters(sourceStart, ...) must not read past the source
+ int count = streamReader.getTextCharacters(4, target, 0, 10);
+ assertThat(count).isEqualTo(3);
+ assertThat(new String(target, 0, count)).isEqualTo("ent");
+ }
+
+ private void advanceToCharacters() throws Exception {
+ while (streamReader.getEventType() != XMLStreamConstants.CHARACTERS) {
+ streamReader.next();
+ }
+ }
+
}
From ea2a26206cc109c9dd5c280e3250b40c90848ee0 Mon Sep 17 00:00:00 2001
From: Sam Brannen <104798+sbrannen@users.noreply.github.com>
Date: Fri, 4 Sep 2026 15:07:07 +0200
Subject: [PATCH 3/3] Polish contribution
See gh-36914
---
.../util/xml/XMLEventStreamReaderTests.java | 27 ++++++++++++-------
1 file changed, 18 insertions(+), 9 deletions(-)
diff --git a/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java b/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java
index 0a517e7abea..def40b8be90 100644
--- a/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java
+++ b/spring-core/src/test/java/org/springframework/util/xml/XMLEventStreamReaderTests.java
@@ -28,6 +28,8 @@ import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamResult;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import org.w3c.dom.Node;
import org.xmlunit.util.Predicate;
@@ -71,17 +73,24 @@ class XMLEventStreamReaderTests {
assertThat(XmlContent.from(writer)).isSimilarTo(XML, nodeFilter);
}
- @Test // gh-36914
- void getTextCharactersHonorsSourceStart() throws Exception {
- char[] target = new char[10];
-
+ @ParameterizedTest // gh-36914
+ @CsvSource(textBlock = """
+ 0, content
+ 1, ontent
+ 4, ent
+ 6, t
+ 7, ''
+ """)
+ void getTextCharactersHonorsSourceStart(int sourceStart, String expected) throws Exception {
advanceToCharacters();
- // text node is "content" (7 chars); copy from index 4 with an oversized buffer
- // getTextCharacters(sourceStart, ...) must not read past the source
- int count = streamReader.getTextCharacters(4, target, 0, 10);
- assertThat(count).isEqualTo(3);
- assertThat(new String(target, 0, count)).isEqualTo("ent");
+ // text node is "content" (7 chars); request an oversized buffer to ensure
+ // getTextCharacters(sourceStart, ...) does not read past the source
+ char[] target = new char[10];
+ int count = streamReader.getTextCharacters(sourceStart, target, 0, 10);
+
+ assertThat(count).isEqualTo(expected.length());
+ assertThat(new String(target, 0, count)).isEqualTo(expected);
}
private void advanceToCharacters() throws Exception {