woodstox icon indicating copy to clipboard operation
woodstox copied to clipboard

Non-conformant `XMLEventFactory.setLocation(null)`

Open stanio opened this issue 3 months ago • 1 comments

The Woodstox XMLEventFactory implementation produces XMLEvents with a null location, while XMLEvent.location should never be null.

XMLEvent.getLocation():

Return the location of this event. The Location returned from this method is non-volatile and will retain its information.

XMLEventFactory.setLocation():

This method allows setting of the Location on each event that is created by this factory. The values are copied by value into the events created by this factory. To reset the location information set the location to null.

To reset the location information set the location to null – doesn't mean the events should end up with a null Location. Rather subsequent events would get an "unknown location" that may be a shared immutable object. This is the case before/without setting location information, also.

The values are copied by value into the events created by this factory – the properties from the given Location object are copied. The given Location object should not be used by reference unless the factory can identify it as an immutable type.

Steps to Reproduce

XMLEventFactoryTest.java:
import java.util.ArrayList;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.stream.Location;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.events.XMLEvent;

public class XMLEventFactoryTest {

    public static void main(String[] args) throws Exception {
        XMLEventFactory factory = XMLEventFactory.newInstance();

        List<XMLEvent> events = new ArrayList<>();
        events.add(factory.createStartDocument());
        events.add(factory.createStartElement(XMLConstants
                .DEFAULT_NS_PREFIX, XMLConstants.NULL_NS_URI, "foo"));

        factory.setLocation(UnknownLocation.INSTANCE);
        events.add(factory.createStartElement(XMLConstants
                .DEFAULT_NS_PREFIX, XMLConstants.NULL_NS_URI, "bar"));
        events.add(factory.createEndElement(XMLConstants
                .DEFAULT_NS_PREFIX, XMLConstants.NULL_NS_URI, "bar"));

        VolatileLocation location = new VolatileLocation(3, 1);
        factory.setLocation(location);
        events.add(factory.createEndElement(XMLConstants
                .DEFAULT_NS_PREFIX, XMLConstants.NULL_NS_URI, "foo"));
        events.add(factory.createEndDocument());

        location.col = 7;
        events.forEach(item ->
                System.out.println(toString(item)));
    }

    private static String toString(XMLEvent event) {
        return String.format("%s(location: %s)",
                             event.getClass().getSimpleName(),
                             toString(event.getLocation()));
    }

    private static String toString(Location location) {
        if (location == null) return "null";

        return location.getClass().getSimpleName()
                + "(lineNumber: " + location.getLineNumber()
                + ", columnNumber: " + location.getColumnNumber() + ")";
    }

    private static class UnknownLocation implements Location {
        static final Location INSTANCE = new UnknownLocation();
        @Override public String getPublicId() { return null; }
        @Override public String getSystemId() { return null; }
        @Override public int getLineNumber() { return -1; }
        @Override public int getColumnNumber() { return -1; }
        @Override public int getCharacterOffset() { return -1; }
    }

    private static class VolatileLocation implements Location {
        int line = -1;
        int col = -1;
        VolatileLocation(int line, int col) {
            this.line = line;
            this.col = col;
        }
        @Override public String getSystemId() { return null; }
        @Override public String getPublicId() { return null; }
        @Override public int getLineNumber() { return line; }
        @Override public int getColumnNumber() { return col; }
        @Override public int getCharacterOffset() { return -1; }
    }

}
> java --module-path <wstx-libs> XMLEventFactoryTest.java

Actual Behavior

StartDocumentEventImpl(location: null)
SimpleStartElement(location: null)
SimpleStartElement(location: UnknownLocation(lineNumber: -1, columnNumber: -1))
EndElementEventImpl(location: UnknownLocation(lineNumber: -1, columnNumber: -1))
EndElementEventImpl(location: VolatileLocation(lineNumber: 3, columnNumber: 7))
EndDocumentEventImpl(location: VolatileLocation(lineNumber: 3, columnNumber: 7))

Expected Behavior

StartDocumentEventImpl(location: WoodstoxNonVolatileLocation(lineNumber: -1, columnNumber: -1))
SimpleStartElement(location: WoodstoxNonVolatileLocation(lineNumber: -1, columnNumber: -1))
SimpleStartElement(location: WoodstoxNonVolatileLocation(lineNumber: -1, columnNumber: -1))
EndElementEventImpl(location: WoodstoxNonVolatileLocation(lineNumber: -1, columnNumber: -1))
EndElementEventImpl(location: WoodstoxNonVolatileLocation(lineNumber: 3, columnNumber: 1))
EndDocumentEventImpl(location: WoodstoxNonVolatileLocation(lineNumber: 3, columnNumber: 1))

The WoodstoxNonVolatileLocation type is just an example name suggesting it should be a Woodstox implementation.


The full context of my original use case is JDK-8329424:

  • Transform a source to a StAX Result, a custom XMLEvent buffer;
  • Reuse the XMLEvent buffer as a source for subsequent transformation.

Even with the workaround for the JDK bug, when Woodstox is plugged in as the StAX provider, the subsequent transformation fails because of the events null location:

java.lang.NullPointerException: Cannot invoke "javax.xml.stream.Location.getSystemId()" because the return value of "javax.xml.stream.events.XMLEvent.getLocation()" is null
	at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.StAXEvent2SAX$1.getSystemId(StAXEvent2SAX.java:286)
	at java.xml/com.sun.org.apache.xml.internal.dtm.ref.sax2dtm.SAX2DTM.setDocumentLocator(SAX2DTM.java:1610)
	at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.StAXEvent2SAX.handleStartDocument(StAXEvent2SAX.java:275)
	...
	at TransformPipelineTest.main(TransformPipelineTest.java:53)

stanio avatar Apr 02 '24 05:04 stanio