Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-conformant XMLEventFactory.setLocation(null) #204

Open
stanio opened this issue Apr 2, 2024 · 1 comment
Open

Non-conformant XMLEventFactory.setLocation(null) #204

stanio opened this issue Apr 2, 2024 · 1 comment
Labels
pr-welcome Issue for which progress most likely if someone submits a Pull Request

Comments

@stanio
Copy link
Contributor

stanio commented Apr 2, 2024

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)
@cowtowncoder
Copy link
Member

Sounds like a bug indeed.

@cowtowncoder cowtowncoder added the pr-welcome Issue for which progress most likely if someone submits a Pull Request label Apr 6, 2024
@cowtowncoder cowtowncoder changed the title Non-conformant XMLEventFactory.setLocation(null) Non-conformant XMLEventFactory.setLocation(null) Apr 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr-welcome Issue for which progress most likely if someone submits a Pull Request
Projects
None yet
Development

No branches or pull requests

2 participants