From dbef4a9831a14e7c1d47ca9aa43e2bddaa20f5d0 Mon Sep 17 00:00:00 2001 From: Stanimir Stamenkov <1247730+stanio@users.noreply.github.com> Date: Tue, 30 Apr 2024 05:52:43 +0300 Subject: [PATCH] WstxEventFactory: Ensure events' immutable non-null location (#205) --- release-notes/CREDITS | 5 + release-notes/VERSION | 2 + .../com/ctc/wstx/stax/WstxEventFactory.java | 25 +++++ .../evt/TestEventFactoryLocation.java | 102 ++++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 src/test/java/wstxtest/evt/TestEventFactoryLocation.java diff --git a/release-notes/CREDITS b/release-notes/CREDITS index 66d11da6..013d3e6a 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -104,3 +104,8 @@ Alexey Gavrilov (@agavrilov76) Jeremy Norris (@norrisjeremy) * Reported #200: Fix shading of `isorelax` dependency (and other msv-core components) (6.6.2) + +Stanimir Stamenkov (@stanio) + +* Reported, provided fix for #204: Non-conformant `XMLEventFactory.setLocation(null)` + (7.0.0) diff --git a/release-notes/VERSION b/release-notes/VERSION index 007ff9bf..c3407a5f 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -10,6 +10,8 @@ Project: woodstox #194: Remove `QNameCreator` compatibility class #196: WstxSAXParser error handling when used with JAXB validation (reported by @winfriedgerlach) +#204: Non-conformant `XMLEventFactory.setLocation(null)` + (fix provided by Stanimir S) 6.6.2 (26-Mar-2024) diff --git a/src/main/java/com/ctc/wstx/stax/WstxEventFactory.java b/src/main/java/com/ctc/wstx/stax/WstxEventFactory.java index 0a21323a..19cf80e5 100644 --- a/src/main/java/com/ctc/wstx/stax/WstxEventFactory.java +++ b/src/main/java/com/ctc/wstx/stax/WstxEventFactory.java @@ -23,9 +23,11 @@ import javax.xml.stream.events.*; import aQute.bnd.annotation.spi.ServiceProvider; +import org.codehaus.stax2.XMLStreamLocation2; import org.codehaus.stax2.ri.Stax2EventFactoryImpl; import com.ctc.wstx.evt.*; +import com.ctc.wstx.io.WstxInputLocation; /** * Implementation of {@link XMLEventFactory} to be used with @@ -37,6 +39,7 @@ public final class WstxEventFactory { public WstxEventFactory() { super(); + super.setLocation(WstxInputLocation.getEmptyLocation()); } /* @@ -45,6 +48,28 @@ public WstxEventFactory() { ///////////////////////////////////////////////////////////// */ + @Override + public void setLocation(Location location) { + super.setLocation(location == null ? WstxInputLocation.getEmptyLocation() + : immutableLocation(location)); + } + + private static WstxInputLocation immutableLocation(Location location) { + if (location == null) { + return null; + } + if (location.getClass() == WstxInputLocation.class) { + return (WstxInputLocation) location; + } + + WstxInputLocation context = (location instanceof XMLStreamLocation2) + ? immutableLocation(((XMLStreamLocation2) location).getContext()) + : null; + return new WstxInputLocation(context, location.getPublicId(), + location.getSystemId(), location.getCharacterOffset(), + location.getLineNumber(), location.getColumnNumber()); + } + //public Attribute createAttribute(QName name, String value) //public Attribute createAttribute(String localName, String value) //public Attribute createAttribute(String prefix, String nsURI, String localName, String value) diff --git a/src/test/java/wstxtest/evt/TestEventFactoryLocation.java b/src/test/java/wstxtest/evt/TestEventFactoryLocation.java new file mode 100644 index 00000000..a0948b5b --- /dev/null +++ b/src/test/java/wstxtest/evt/TestEventFactoryLocation.java @@ -0,0 +1,102 @@ +package wstxtest.evt; + +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Objects; + +import javax.xml.stream.Location; +import javax.xml.stream.events.XMLEvent; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.junit.Before; +import org.junit.Test; + +import com.ctc.wstx.io.WstxInputLocation; +import com.ctc.wstx.stax.WstxEventFactory; + +public class TestEventFactoryLocation { + + private WstxEventFactory eventFactory; + + @Before + public void setUp() { + eventFactory = new WstxEventFactory(); + } + + @Test + public void testDefaultLocation() { + XMLEvent event = eventFactory.createStartDocument(); + + assertThat("event.location", event.getLocation(), unknownLocation()); + } + + @Test + public void testResetLocation() { + eventFactory.setLocation(new WstxInputLocation(null, null, "about:blank", 3L, 2, 1)); + eventFactory.setLocation(null); + + XMLEvent event = eventFactory.createStartElement("foo", "bar", "baz"); + + assertThat("event.location", event.getLocation(), unknownLocation()); + } + + @Test + public void testNonVolatileLocation() { + VolatileLocation volatileLocation = new VolatileLocation(2, 3); + eventFactory.setLocation(volatileLocation); + + XMLEvent event = eventFactory.createEndElement("foo", "bar", "baz"); + volatileLocation.line = 4; + volatileLocation.col = 5; + + assertThat("event.location", event.getLocation(), + locationWithProperties(null, null, -1, 2, 3)); + } + + private static Matcher unknownLocation() { + // XXX: Not sure if the empty-string publicId/systemId are conformant + //return locationWithProperties(null, null, -1, -1, -1); + return locationWithProperties("", "", -1, -1, -1); + } + + private static Matcher locationWithProperties(String pubId, + String sysId, int charOffset, int row, int col) { + return new TypeSafeMatcher() { + @Override public void describeTo(Description description) { + description.appendText("Location(publicId: ").appendValue(pubId) + .appendText(", systemId: ").appendValue(sysId) + .appendText(", characterOffset: ").appendValue(charOffset) + .appendText(", lineNumber: ").appendValue(row) + .appendText(", columnNumber: ").appendValue(col); + } + + @Override protected boolean matchesSafely(Location item) { + return Objects.equals(item.getPublicId(), pubId) + && Objects.equals(item.getSystemId(), sysId) + && Objects.equals(item.getCharacterOffset(), charOffset) + && Objects.equals(item.getLineNumber(), row) + && Objects.equals(item.getColumnNumber(), col); + } + }; + } + + + static class VolatileLocation implements Location { + int line; + int col; + VolatileLocation(int line, int col) { + this.line = line; + this.col = col; + } + @Override public String getPublicId() { return null; } + @Override public String getSystemId() { return null; } + @Override public int getLineNumber() { return line; } + @Override public int getColumnNumber() { return col; } + @Override public int getCharacterOffset() { return -1; } + } + + +} +