8144967: javax.xml.transform.Source and org.xml.sax.InputSource can be empty
authorjoehw
Fri, 08 Jan 2016 10:51:34 -0800
changeset 34983 cab976ee6f21
parent 34918 80f67512daa1
child 34984 48a409c654e9
8144967: javax.xml.transform.Source and org.xml.sax.InputSource can be empty Reviewed-by: darcy, rriggs
jaxp/src/java.xml/share/classes/javax/xml/transform/Source.java
jaxp/src/java.xml/share/classes/javax/xml/transform/dom/DOMSource.java
jaxp/src/java.xml/share/classes/javax/xml/transform/sax/SAXSource.java
jaxp/src/java.xml/share/classes/javax/xml/transform/stax/StAXSource.java
jaxp/src/java.xml/share/classes/javax/xml/transform/stream/StreamSource.java
jaxp/src/java.xml/share/classes/org/xml/sax/InputSource.java
jaxp/test/javax/xml/jaxp/unittest/common/Sources.java
--- a/jaxp/src/java.xml/share/classes/javax/xml/transform/Source.java	Wed Jul 05 21:12:04 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/javax/xml/transform/Source.java	Fri Jan 08 10:51:34 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -52,4 +52,17 @@
      * if setSystemId was not called.
      */
     public String getSystemId();
+
+    /**
+     * Indicates whether the {@code Source} object is empty. Empty means
+     * that there is no input available from this Source.
+     *
+     * @implSpec The default implementation of this method throws
+     * {@link UnsupportedOperationException}.
+     *
+     * @return true if the {@code Source} object is empty, false otherwise
+     */
+    default boolean isEmpty() {
+        throw new UnsupportedOperationException("The isEmpty method is not supported.");
+    }
 }
--- a/jaxp/src/java.xml/share/classes/javax/xml/transform/dom/DOMSource.java	Wed Jul 05 21:12:04 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/javax/xml/transform/dom/DOMSource.java	Fri Jan 08 10:51:34 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -122,6 +122,7 @@
      *
      * @param systemID Base URL for this DOM tree.
      */
+    @Override
     public void setSystemId(String systemID) {
         this.systemID = systemID;
     }
@@ -132,7 +133,25 @@
      *
      * @return Base URL for this DOM tree.
      */
+    @Override
     public String getSystemId() {
         return this.systemID;
     }
+
+    /**
+     * Indicates whether the {@code DOMSource} object is empty. Empty is
+     * defined as follows:
+     * <ul>
+     * <li>if the system identifier and node are {@code null};
+     * </li>
+     * <li>if the system identifier is null, and the {@code node} has no child nodes.
+     * </li>
+     * </ul>
+     *
+     * @return true if the {@code DOMSource} object is empty, false otherwise
+     */
+    @Override
+    public boolean isEmpty() {
+        return systemID == null && (node == null || !node.hasChildNodes());
+    }
 }
--- a/jaxp/src/java.xml/share/classes/javax/xml/transform/sax/SAXSource.java	Wed Jul 05 21:12:04 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/javax/xml/transform/sax/SAXSource.java	Fri Jan 08 10:51:34 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -147,6 +147,7 @@
      *
      * @param systemId The system identifier as a URI string.
      */
+    @Override
     public void setSystemId(String systemId) {
 
         if (null == inputSource) {
@@ -162,6 +163,7 @@
      *
      * @return Base URL for the <code>Source</code>, or <code>null</code>.
      */
+    @Override
     public String getSystemId() {
 
         if (inputSource == null) {
@@ -207,4 +209,22 @@
             return null;
         }
     }
+
+    /**
+     * Indicates whether the {@code SAXSource} object is empty. Empty is
+     * defined as follows:
+     * <ul>
+     * <li>if the system identifier and {@code InputSource} are {@code null};
+     * </li>
+     * <li>if the system identifier is {@code null}, and the {@code InputSource}
+     * is empty.
+     * </li>
+     * </ul>
+     *
+     * @return true if the {@code SAXSource} object is empty, false otherwise
+     */
+    @Override
+    public boolean isEmpty() {
+        return getSystemId() == null && (inputSource == null || inputSource.isEmpty());
+    }
 }
--- a/jaxp/src/java.xml/share/classes/javax/xml/transform/stax/StAXSource.java	Wed Jul 05 21:12:04 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/javax/xml/transform/stax/StAXSource.java	Fri Jan 08 10:51:34 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -209,6 +209,7 @@
      * @throws UnsupportedOperationException Is <strong>always</strong>
      *   thrown by this method.
      */
+    @Override
     public void setSystemId(final String systemId) {
 
         throw new UnsupportedOperationException(
@@ -229,8 +230,21 @@
      *
      * @return System identifier used by this <code>StAXSource</code>.
      */
+    @Override
     public String getSystemId() {
 
         return systemId;
     }
+
+    /**
+     * Indicates whether the {@code StAXSource} object is empty. Since a
+     * {@code StAXSource} object can never be empty, this method always returns
+     * false.
+     *
+     * @return unconditionally false
+     */
+    @Override
+    public boolean isEmpty() {
+        return false;
+    }
 }
--- a/jaxp/src/java.xml/share/classes/javax/xml/transform/stream/StreamSource.java	Wed Jul 05 21:12:04 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/javax/xml/transform/stream/StreamSource.java	Fri Jan 08 10:51:34 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,8 +26,10 @@
 package javax.xml.transform.stream;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
+import javax.xml.transform.Result;
 
 import javax.xml.transform.Source;
 
@@ -233,6 +235,7 @@
      *
      * @param systemId The system identifier as a URL string.
      */
+    @Override
     public void setSystemId(String systemId) {
         this.systemId = systemId;
     }
@@ -243,6 +246,7 @@
      * @return The system identifier that was set with setSystemId, or null
      * if setSystemId was not called.
      */
+    @Override
     public String getSystemId() {
         return systemId;
     }
@@ -259,6 +263,59 @@
         this.systemId = f.toURI().toASCIIString();
     }
 
+    /**
+     * Indicates whether the {@code StreamSource} object is empty. Empty is
+     * defined as follows:
+     * <ul>
+     * <li>All of the input sources, including the public identifier, system
+     * identifier, byte stream, and character stream, are {@code null}.
+     * </li>
+     * <li>The public identifier and system identifier are {@code null}, and
+     * byte and character stream are either {@code null} or contain no byte or
+     * character.
+     * <p>
+     * Note that this method will reset the byte stream if it is provided, or
+     * the character stream if the byte stream is not provided.
+     * </li>
+     * </ul>
+     * <p>
+     * In case of error while checking the byte or character stream, the method
+     * will return false to allow the XML processor to handle the error.
+     *
+     * @return true if the {@code StreamSource} object is empty, false otherwise
+     */
+    @Override
+    public boolean isEmpty() {
+        return (publicId == null && systemId == null && isStreamEmpty());
+    }
+
+    private boolean isStreamEmpty() {
+        boolean empty = true;
+        try {
+            if (inputStream != null) {
+                inputStream.reset();
+                int bytesRead = inputStream.available();
+                if (bytesRead > 0) {
+                    return false;
+                }
+            }
+
+            if (reader != null) {
+                reader.reset();
+                int c = reader.read();
+                reader.reset();
+                if (c != -1) {
+                    return false;
+                }
+            }
+        } catch (IOException ex) {
+            //in case of error, return false
+            return false;
+        }
+
+        return empty;
+    }
+
     //////////////////////////////////////////////////////////////////////
     // Internal state.
     //////////////////////////////////////////////////////////////////////
--- a/jaxp/src/java.xml/share/classes/org/xml/sax/InputSource.java	Wed Jul 05 21:12:04 2017 +0200
+++ b/jaxp/src/java.xml/share/classes/org/xml/sax/InputSource.java	Fri Jan 08 10:51:34 2016 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -30,6 +30,7 @@
 
 package org.xml.sax;
 
+import java.io.IOException;
 import java.io.Reader;
 import java.io.InputStream;
 
@@ -343,8 +344,57 @@
         return characterStream;
     }
 
+    /**
+     * Indicates whether the {@code InputSource} object is empty. Empty is
+     * defined as follows:
+     * <ul>
+     * <li>All of the input sources, including the public identifier, system
+     * identifier, byte stream, and character stream, are {@code null}.
+     * </li>
+     * <li>The public identifier and system identifier are  {@code null}, and
+     * byte and character stream are either  {@code null} or contain no byte
+     * or character.
+     * <p>
+     * Note that this method will reset the byte stream if it is provided, or
+     * the character stream if the byte stream is not provided.
+     * </li>
+     * </ul>
+     * <p>
+     * In case of error while checking the byte or character stream, the method
+     * will return false to allow the XML processor to handle the error.
+     *
+     * @return true if the {@code InputSource} object is empty, false otherwise
+     */
+    public boolean isEmpty() {
+        return (publicId == null && systemId == null && isStreamEmpty());
+    }
 
+    private boolean isStreamEmpty() {
+        boolean empty = true;
+        try {
+            if (byteStream != null) {
+                byteStream.reset();
+                int bytesRead = byteStream.available();
+                if (bytesRead > 0) {
+                    return false;
+                }
+            }
 
+            if (characterStream != null) {
+                characterStream.reset();
+                int c = characterStream.read();
+                characterStream.reset();
+                if (c != -1) {
+                    return false;
+                }
+            }
+        } catch (IOException ex) {
+            //in case of error, return false
+            return false;
+        }
+
+        return empty;
+    }
     ////////////////////////////////////////////////////////////////////
     // Internal state.
     ////////////////////////////////////////////////////////////////////
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jaxp/test/javax/xml/jaxp/unittest/common/Sources.java	Fri Jan 08 10:51:34 2016 -0800
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package common;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.transform.Source;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.stax.StAXSource;
+import javax.xml.transform.stream.StreamSource;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+
+/*
+ * @bug 8144967
+ * @summary Tests related to the javax.xml.transform.Source
+ * and org.xml.sax.InputSource
+ */
+public class Sources {
+
+    /**
+     * @bug 8144967
+     * Tests whether a Source object is empty
+     * @param source the Source object
+     */
+    @Test(dataProvider = "emptySources")
+    public void testIsEmpty(Source source) {
+        Assert.assertTrue(source.isEmpty(), "The source is not empty");
+    }
+
+    /**
+     * @bug 8144967
+     * Tests that the source is not empty
+     * @param source the Source object
+     */
+    @Test(dataProvider = "nonEmptySources")
+    public void testIsNotEmpty(Source source) {
+        Assert.assertTrue(!source.isEmpty(), "The source is empty");
+    }
+
+    /**
+     * @bug 8144967
+     * Tests whether an InputSource object is empty
+     * @param source the InputSource object
+     */
+    @Test(dataProvider = "emptyInputSource")
+    public void testISIsEmpty(InputSource source) {
+        Assert.assertTrue(source.isEmpty(), "The source is not empty");
+    }
+
+    /*
+     * DataProvider: sources that are empty
+     */
+    @DataProvider(name = "emptySources")
+    Object[][] getSources() throws URISyntaxException {
+
+        return new Object[][]{
+            {new DOMSource()},
+            {new DOMSource(getDocument())},
+            {new SAXSource()},
+            {new SAXSource(new InputSource(new StringReader("")))},
+            {new SAXSource(getXMLReader(), new InputSource(new StringReader("")))},
+            {new StreamSource()},
+            {new StreamSource(new ByteArrayInputStream("".getBytes()))},
+            {new StreamSource(new StringReader(""))},
+            {new StreamSource(new StringReader(""), null)},
+            {new StreamSource((String) null)}
+        };
+    }
+
+    /*
+     * DataProvider: sources that are not empty
+     */
+    @DataProvider(name = "nonEmptySources")
+    Object[][] getSourcesEx() throws URISyntaxException {
+        StAXSource ss = null;
+        try {
+            ss = new StAXSource(getXMLEventReader());
+        } catch (XMLStreamException ex) {}
+
+        return new Object[][]{
+            //This will set a non-null systemId on the resulting StreamSource
+            {new StreamSource(new File(""))},
+            //Can't tell because XMLStreamReader is a pull parser, cursor advancement
+            //would have been required in order to examine the reader.
+            {new StAXSource(getXMLStreamReader())},
+            {ss}
+        };
+    }
+
+    /*
+     * DataProvider: sources that are empty
+     */
+    @DataProvider(name = "emptyInputSource")
+    Object[][] getInputSources() throws URISyntaxException {
+        byte[] utf8Bytes = null;
+        try {
+            utf8Bytes = "".getBytes("UTF8");
+        } catch (UnsupportedEncodingException ex) {
+            throw new RuntimeException(ex.getMessage());
+        }
+        return new Object[][]{
+            {new InputSource()},
+            {new InputSource(new ByteArrayInputStream(utf8Bytes))},
+            {new InputSource(new StringReader(""))},
+            {new InputSource((String) null)}
+        };
+    }
+
+    /**
+     * Returns an instance of Document.
+     *
+     * @return an instance of Document.
+     */
+    private Document getDocument() {
+        Document doc = null;
+        try {
+            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+            doc = dbf.newDocumentBuilder().newDocument();
+        } catch (ParserConfigurationException ex) {}
+        return doc;
+    }
+
+    /**
+     * Returns an instance of XMLReader.
+     *
+     * @return an instance of XMLReader.
+     */
+    private XMLReader getXMLReader() {
+        XMLReader reader = null;
+        try {
+            reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
+        } catch (ParserConfigurationException | SAXException ex) {}
+        return reader;
+    }
+
+    /**
+     * Returns an instance of XMLStreamReader.
+     *
+     * @return an instance of XMLStreamReader.
+     */
+    private XMLStreamReader getXMLStreamReader() {
+        XMLStreamReader r = null;
+        try {
+            XMLInputFactory xif = XMLInputFactory.newInstance();
+            r = xif.createXMLStreamReader(new ByteArrayInputStream("".getBytes()));
+        } catch (XMLStreamException ex) {}
+
+        return r;
+    }
+
+    /**
+     * Returns an instance of XMLEventReader.
+     *
+     * @return an instance of XMLEventReader.
+     */
+    private XMLEventReader getXMLEventReader() {
+        XMLEventReader r = null;
+        try {
+            r = XMLInputFactory.newInstance().createXMLEventReader(
+                            new ByteArrayInputStream("".getBytes()));
+        } catch (XMLStreamException ex) {}
+
+        return r;
+    }
+}