Prevent NullPointerException in ReversedLinesFileReader constructors
MODIFY
src/changes/changes.xml
src/changes/changes.xml
@@ -127,6 +127,9 @@ The <action> type attribute can be add,update,fix,remove.
<action issue="IO-676" dev="ggregory" type="add" due-to="Isira Seneviratne, Gary Gregory">
Add isFileNewer() and isFileOlder() methods that support the Java 8 Date/Time API. #124.
</action>
+ <action issue="IO-676" dev="ggregory" type="fix" due-to="Michael Ernst, Gary Gregory">
+ Prevent NullPointerException in ReversedLinesFileReader constructors #117.
+ </action>
<!-- UPDATES -->
<action dev="ggregory" type="add" due-to="Gary Gregory">
Deprecate IOUtils.LINE_SEPARATOR in favor of Java 7's System.lineSeparator().

MODIFY
src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java
src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java
/**
* ctor
+ *
* @param no the part number
* @param length its length
* @param leftOverOfLastFilePart remainder
@@ -220,8 +221,7 @@ private FilePart rollOver() throws IOException {
* Creates a ReversedLinesFileReader with default block size of 4KB and the
* platform's default encoding.
*
- * @param file
- * the file to be read
+ * @param file the file to be read
* @throws IOException if an I/O error occurs
* @deprecated 2.5 use {@link #ReversedLinesFileReader(File, Charset)} instead
*/
@@ -234,9 +234,8 @@ public ReversedLinesFileReader(final File file) throws IOException {
* Creates a ReversedLinesFileReader with default block size of 4KB and the
* specified encoding.
*
- * @param file
- * the file to be read
- * @param charset the charset to use
+ * @param file the file to be read
+ * @param charset the charset to use, null uses the default Charset.
* @throws IOException if an I/O error occurs
* @since 2.5
*/
@@ -247,45 +246,43 @@ public ReversedLinesFileReader(final File file, final Charset charset) throws IO
/**
* Creates a ReversedLinesFileReader with the given block size and encoding.
*
- * @param file
- * the file to be read
- * @param blockSize
- * size of the internal buffer (for ideal performance this should
- * match with the block size of the underlying file system).
- * @param encoding
- * the encoding of the file
+ * @param file the file to be read
+ * @param blockSize size of the internal buffer (for ideal performance this
+ * should match with the block size of the underlying file
+ * system).
+ * @param charset the encoding of the file, null uses the default Charset.
* @throws IOException if an I/O error occurs
* @since 2.3
*/
- public ReversedLinesFileReader(final File file, final int blockSize, final Charset encoding) throws IOException {
- this(file.toPath(), blockSize, encoding);
+ public ReversedLinesFileReader(final File file, final int blockSize, final Charset charset) throws IOException {
+ this(file.toPath(), blockSize, charset);
}
/**
* Creates a ReversedLinesFileReader with the given block size and encoding.
*
- * @param file
- * the file to be read
- * @param blockSize
- * size of the internal buffer (for ideal performance this should
- * match with the block size of the underlying file system).
- * @param encoding
- * the encoding of the file
+ * @param file the file to be read
+ * @param blockSize size of the internal buffer (for ideal performance this
+ * should match with the block size of the underlying file
+ * system).
+ * @param charsetName the encoding of the file, null uses the default Charset.
* @throws IOException if an I/O error occurs
- * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in
- * version 2.2 if the encoding is not supported.
+ * @throws java.nio.charset.UnsupportedCharsetException thrown instead of
+ * {@link UnsupportedEncodingException}
+ * in version 2.2 if the
+ * encoding is not
+ * supported.
*/
- public ReversedLinesFileReader(final File file, final int blockSize, final String encoding) throws IOException {
- this(file.toPath(), blockSize, encoding);
+ public ReversedLinesFileReader(final File file, final int blockSize, final String charsetName) throws IOException {
+ this(file.toPath(), blockSize, charsetName);
}
/**
* Creates a ReversedLinesFileReader with default block size of 4KB and the
* specified encoding.
*
- * @param file
- * the file to be read
- * @param charset the charset to use
+ * @param file the file to be read
+ * @param charset the charset to use, null uses the default Charset.
* @throws IOException if an I/O error occurs
* @since 2.7
*/
@@ -296,52 +293,53 @@ public ReversedLinesFileReader(final Path file, final Charset charset) throws IO
/**
* Creates a ReversedLinesFileReader with the given block size and encoding.
*
- * @param file
- * the file to be read
- * @param blockSize
- * size of the internal buffer (for ideal performance this should
- * match with the block size of the underlying file system).
- * @param encoding
- * the encoding of the file
+ * @param file the file to be read
+ * @param blockSize size of the internal buffer (for ideal performance this
+ * should match with the block size of the underlying file
+ * system).
+ * @param charset the encoding of the file, null uses the default Charset.
* @throws IOException if an I/O error occurs
* @since 2.7
*/
- public ReversedLinesFileReader(final Path file, final int blockSize, final Charset encoding) throws IOException {
+ public ReversedLinesFileReader(final Path file, final int blockSize, final Charset charset) throws IOException {
this.blockSize = blockSize;
- this.encoding = encoding;
+ this.encoding = Charsets.toCharset(charset);
// --- check & prepare encoding ---
- final Charset charset = Charsets.toCharset(encoding);
- final CharsetEncoder charsetEncoder = charset.newEncoder();
+ final CharsetEncoder charsetEncoder = this.encoding.newEncoder();
final float maxBytesPerChar = charsetEncoder.maxBytesPerChar();
if (maxBytesPerChar == 1f) {
// all one byte encodings are no problem
byteDecrement = 1;
- } else if (charset == StandardCharsets.UTF_8) {
- // UTF-8 works fine out of the box, for multibyte sequences a second UTF-8 byte can never be a newline byte
+ } else if (this.encoding == StandardCharsets.UTF_8) {
+ // UTF-8 works fine out of the box, for multibyte sequences a second UTF-8 byte
+ // can never be a newline byte
// http://en.wikipedia.org/wiki/UTF-8
byteDecrement = 1;
- } else if (charset == Charset.forName("Shift_JIS") || // Same as for UTF-8
+ } else if (this.encoding == Charset.forName("Shift_JIS") || // Same as for UTF-8
// http://www.herongyang.com/Unicode/JIS-Shift-JIS-Encoding.html
- charset == Charset.forName("windows-31j") || // Windows code page 932 (Japanese)
- charset == Charset.forName("x-windows-949") || // Windows code page 949 (Korean)
- charset == Charset.forName("gbk") || // Windows code page 936 (Simplified Chinese)
- charset == Charset.forName("x-windows-950")) { // Windows code page 950 (Traditional Chinese)
+ this.encoding == Charset.forName("windows-31j") || // Windows code page 932 (Japanese)
+ this.encoding == Charset.forName("x-windows-949") || // Windows code page 949 (Korean)
+ this.encoding == Charset.forName("gbk") || // Windows code page 936 (Simplified Chinese)
+ this.encoding == Charset.forName("x-windows-950")) { // Windows code page 950 (Traditional Chinese)
byteDecrement = 1;
- } else if (charset == StandardCharsets.UTF_16BE || charset == StandardCharsets.UTF_16LE) {
- // UTF-16 new line sequences are not allowed as second tuple of four byte sequences,
+ } else if (this.encoding == StandardCharsets.UTF_16BE || this.encoding == StandardCharsets.UTF_16LE) {
+ // UTF-16 new line sequences are not allowed as second tuple of four byte
+ // sequences,
// however byte order has to be specified
byteDecrement = 2;
- } else if (charset == StandardCharsets.UTF_16) {
+ } else if (this.encoding == StandardCharsets.UTF_16) {
throw new UnsupportedEncodingException(
"For UTF-16, you need to specify the byte order (use UTF-16BE or " + "UTF-16LE)");
} else {
throw new UnsupportedEncodingException(
- "Encoding " + encoding + " is not supported yet (feel free to " + "submit a patch)");
+ "Encoding " + charset + " is not supported yet (feel free to " + "submit a patch)");
}
- // NOTE: The new line sequences are matched in the order given, so it is important that \r\n is BEFORE \n
- this.newLineSequences = new byte[][] {"\r\n".getBytes(encoding), "\n".getBytes(encoding), "\r".getBytes(encoding)};
+ // NOTE: The new line sequences are matched in the order given, so it is
+ // important that \r\n is BEFORE \n
+ this.newLineSequences = new byte[][] { "\r\n".getBytes(this.encoding), "\n".getBytes(this.encoding),
+ "\r".getBytes(this.encoding) };
this.avoidNewlineSplitBufferSize = newLineSequences[0].length;
@@ -364,16 +362,17 @@ public ReversedLinesFileReader(final Path file, final int blockSize, final Chars
/**
* Creates a ReversedLinesFileReader with the given block size and encoding.
*
- * @param file
- * the file to be read
- * @param blockSize
- * size of the internal buffer (for ideal performance this should
- * match with the block size of the underlying file system).
- * @param charsetName
- * the encoding of the file
+ * @param file the file to be read
+ * @param blockSize size of the internal buffer (for ideal performance this
+ * should match with the block size of the underlying file
+ * system).
+ * @param charsetName the encoding of the file, null uses the default Charset.
* @throws IOException if an I/O error occurs
- * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link UnsupportedEncodingException} in
- * version 2.2 if the encoding is not supported.
+ * @throws java.nio.charset.UnsupportedCharsetException thrown instead of
+ * {@link UnsupportedEncodingException}
+ * in version 2.2 if the
+ * encoding is not
+ * supported.
* @since 2.7
*/
public ReversedLinesFileReader(final Path file, final int blockSize, final String charsetName) throws IOException {
@@ -421,7 +420,8 @@ public String readLine() throws IOException {
/**
* Returns {@code lineCount} lines of the file from bottom to top.
* <p>
- * If there are less than {@code lineCount} lines in the file, then that's what you get.
+ * If there are less than {@code lineCount} lines in the file, then that's what
+ * you get.
* </p>
* <p>
* Note: You can easily flip the result with {@link Collections#reverse(List)}.
@@ -450,7 +450,8 @@ public String readLine() throws IOException {
/**
* Returns the last {@code lineCount} lines of the file.
* <p>
- * If there are less than {@code lineCount} lines in the file, then that's what you get.
+ * If there are less than {@code lineCount} lines in the file, then that's what
+ * you get.
* </p>
*
* @param lineCount How many lines to read.

MODIFY
src/test/java/org/apache/commons/io/IOUtilsTestCase.java
src/test/java/org/apache/commons/io/IOUtilsTestCase.java
import org.apache.commons.io.function.IOConsumer;
import org.apache.commons.io.input.NullInputStream;
-import org.apache.commons.io.input.TestResources;
import org.apache.commons.io.output.AppendableWriter;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.io.output.StringBuilderWriter;

RENAME
src/test/java/org/apache/commons/io/input/TestResources.java
src/test/java/org/apache/commons/io/TestResources.java
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.commons.io.input;
+package org.apache.commons.io;
import java.io.File;
import java.io.InputStream;

MODIFY
src/test/java/org/apache/commons/io/input/CharSequenceReaderTest.java
src/test/java/org/apache/commons/io/input/CharSequenceReaderTest.java
import java.nio.CharBuffer;
import java.util.Arrays;
+import org.apache.commons.io.TestResources;
import org.junit.jupiter.api.Test;
/**

MODIFY
src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestParamBlockSize.java
src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestParamBlockSize.java
import java.net.URISyntaxException;
import java.util.stream.IntStream;
+import org.apache.commons.io.TestResources;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;

MODIFY
src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestParamFile.java
src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestParamFile.java
import java.io.BufferedReader;
import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Stack;
import java.util.stream.Stream;
+import org.apache.commons.io.Charsets;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.TestResources;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
*/
public class ReversedLinesFileReaderTestParamFile {
- public static Stream<Arguments> testDataIntegrityWithBufferedReader() {
- return Stream.of(
- Arguments.of("test-file-20byteslength.bin", "ISO_8859_1", null, false),
- Arguments.of("test-file-iso8859-1-shortlines-win-linebr.bin", "ISO_8859_1", null, false),
- Arguments.of("test-file-iso8859-1.bin", "ISO_8859_1", null, false),
- Arguments.of("test-file-shiftjis.bin", "Shift_JIS", null, false),
- Arguments.of("test-file-utf16be.bin", "UTF-16BE", null, false),
- Arguments.of("test-file-utf16le.bin", "UTF-16LE", null, false),
- Arguments.of("test-file-utf8-cr-only.bin", "UTF-8", null, false),
- Arguments.of("test-file-utf8-win-linebr.bin", "UTF-8", null, false),
- Arguments.of("test-file-utf8-win-linebr.bin", "UTF-8", 1, false),
- Arguments.of("test-file-utf8-win-linebr.bin", "UTF-8", 2, false),
- Arguments.of("test-file-utf8-win-linebr.bin", "UTF-8", 3, false),
- Arguments.of("test-file-utf8-win-linebr.bin", "UTF-8", 4, false),
- Arguments.of("test-file-utf8.bin", "UTF-8", null, false),
- Arguments.of("test-file-utf8.bin", "UTF-8", null, true),
- Arguments.of("test-file-windows-31j.bin", "windows-31j", null, false),
- Arguments.of("test-file-gbk.bin", "gbk", null, false),
- Arguments.of("test-file-x-windows-949.bin", "x-windows-949", null, false),
- Arguments.of("test-file-x-windows-950.bin", "x-windows-950", null, false)
- );
+ public static Stream<Arguments> testDataIntegrityWithBufferedReader() throws IOException, URISyntaxException {
+ // Make a file using the default encoding.
+ Path sourcePath = TestResources.getPath("test-file-utf8-win-linebr.bin");
+ Path targetPath = Files.createTempFile("ReversedLinesFileReaderTestParamFile", ".bin");
+ try (Reader input = Files.newBufferedReader(sourcePath, StandardCharsets.UTF_8);
+ Writer output = Files.newBufferedWriter(targetPath, Charset.defaultCharset())) {
+ IOUtils.copyLarge(input, output);
+ }
+ // All tests
+ return Stream.of(Arguments.of(targetPath.toAbsolutePath().toString(), null, null, false, false),
+ Arguments.of("test-file-20byteslength.bin", "ISO_8859_1", null, false, true),
+ Arguments.of("test-file-iso8859-1-shortlines-win-linebr.bin", "ISO_8859_1", null, false, true),
+ Arguments.of("test-file-iso8859-1.bin", "ISO_8859_1", null, false, true),
+ Arguments.of("test-file-shiftjis.bin", "Shift_JIS", null, false, true),
+ Arguments.of("test-file-utf16be.bin", "UTF-16BE", null, false, true),
+ Arguments.of("test-file-utf16le.bin", "UTF-16LE", null, false, true),
+ Arguments.of("test-file-utf8-cr-only.bin", "UTF-8", null, false, true),
+ Arguments.of("test-file-utf8-win-linebr.bin", "UTF-8", null, false, true,
+ Arguments.of("test-file-utf8-win-linebr.bin", "UTF-8", 1, false, true),
+ Arguments.of("test-file-utf8-win-linebr.bin", "UTF-8", 2, false, true),
+ Arguments.of("test-file-utf8-win-linebr.bin", "UTF-8", 3, false, true),
+ Arguments.of("test-file-utf8-win-linebr.bin", "UTF-8", 4, false, true),
+ Arguments.of("test-file-utf8.bin", "UTF-8", null, false, true),
+ Arguments.of("test-file-utf8.bin", "UTF-8", null, true, true),
+ Arguments.of("test-file-windows-31j.bin", "windows-31j", null, false, true),
+ Arguments.of("test-file-gbk.bin", "gbk", null, false, true),
+ Arguments.of("test-file-x-windows-949.bin", "x-windows-949", null, false, true),
+ Arguments.of("test-file-x-windows-950.bin", "x-windows-950", null, false, true)));
}
- @ParameterizedTest(name = "{0}, encoding={1}, blockSize={2}, useNonDefaultFileSystem={3}")
+ @ParameterizedTest(name = "{0}, encoding={1}, blockSize={2}, useNonDefaultFileSystem={3}, isResource={4}")
@MethodSource
- public void testDataIntegrityWithBufferedReader(final String fileName, final String encodingName,
- final Integer blockSize, final boolean useNonDefaultFileSystem) throws IOException, URISyntaxException {
+ public void testDataIntegrityWithBufferedReader(final String fileName, final String charsetName,
+ final Integer blockSize, final boolean useNonDefaultFileSystem, final boolean isResource)
+ throws IOException, URISyntaxException {
- Path filePath = TestResources.getPath(fileName);
+ Path filePath = isResource ? TestResources.getPath(fileName) : Paths.get(fileName);
FileSystem fileSystem = null;
if (useNonDefaultFileSystem) {
fileSystem = Jimfs.newFileSystem(Configuration.unix());
filePath = Files.copy(filePath, fileSystem.getPath("/" + fileName));
}
- final Charset charset = Charset.forName(encodingName);
+ // We want to test null Charset in the ReversedLinesFileReader ctor.
+ final Charset charset = charsetName != null ? Charset.forName(charsetName) : null;
try (ReversedLinesFileReader reversedLinesFileReader = blockSize == null
? new ReversedLinesFileReader(filePath, charset)
: new ReversedLinesFileReader(filePath, blockSize, charset)) {
@@ -83,7 +99,7 @@ public void testDataIntegrityWithBufferedReader(final String fileName, final Str
final Stack<String> lineStack = new Stack<>();
String line;
- try (BufferedReader bufferedReader = Files.newBufferedReader(filePath, charset)) {
+ try (BufferedReader bufferedReader = Files.newBufferedReader(filePath, Charsets.toCharset(charset))) {
// read all lines in normal order
while ((line = bufferedReader.readLine()) != null) {
lineStack.push(line);

MODIFY
src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestSimple.java
src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestSimple.java
import java.util.List;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.TestResources;
import org.junit.jupiter.api.Test;
public class ReversedLinesFileReaderTestSimple {

MODIFY
src/test/java/org/apache/commons/io/input/TailerTest.java
src/test/java/org/apache/commons/io/input/TailerTest.java
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.TestResources;
import org.apache.commons.io.test.TestUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

Prevent NullPointerException in ReversedLinesFileReader constructors
#117.