8235792: LineNumberReader.getLineNumber() behavior is inconsistent with respect to EOF

Reviewed-by: alanb, darcy, rriggs
This commit is contained in:
Brian Burkhalter 2020-08-05 11:40:07 -07:00
parent 339016a0f2
commit 3ea5fdc9ac
3 changed files with 136 additions and 24 deletions

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -302,6 +302,8 @@ public class BufferedReader extends Reader {
* (EOF). * (EOF).
* *
* @param ignoreLF If true, the next '\n' will be skipped * @param ignoreLF If true, the next '\n' will be skipped
* @param term Output: Whether a line terminator was encountered
* while reading the line; may be {@code null}.
* *
* @return A String containing the contents of the line, not including * @return A String containing the contents of the line, not including
* any line-termination characters, or null if the end of the * any line-termination characters, or null if the end of the
@ -311,13 +313,14 @@ public class BufferedReader extends Reader {
* *
* @throws IOException If an I/O error occurs * @throws IOException If an I/O error occurs
*/ */
String readLine(boolean ignoreLF) throws IOException { String readLine(boolean ignoreLF, boolean[] term) throws IOException {
StringBuilder s = null; StringBuilder s = null;
int startChar; int startChar;
synchronized (lock) { synchronized (lock) {
ensureOpen(); ensureOpen();
boolean omitLF = ignoreLF || skipLF; boolean omitLF = ignoreLF || skipLF;
if (term != null) term[0] = false;
bufferLoop: bufferLoop:
for (;;) { for (;;) {
@ -344,6 +347,7 @@ public class BufferedReader extends Reader {
for (i = nextChar; i < nChars; i++) { for (i = nextChar; i < nChars; i++) {
c = cb[i]; c = cb[i];
if ((c == '\n') || (c == '\r')) { if ((c == '\n') || (c == '\r')) {
if (term != null) term[0] = true;
eol = true; eol = true;
break charLoop; break charLoop;
} }
@ -389,7 +393,7 @@ public class BufferedReader extends Reader {
* @see java.nio.file.Files#readAllLines * @see java.nio.file.Files#readAllLines
*/ */
public String readLine() throws IOException { public String readLine() throws IOException {
return readLine(false); return readLine(false, null);
} }
/** /**

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,7 +25,6 @@
package java.io; package java.io;
/** /**
* A buffered character-input stream that keeps track of line numbers. This * A buffered character-input stream that keeps track of line numbers. This
* class defines methods {@link #setLineNumber(int)} and {@link * class defines methods {@link #setLineNumber(int)} and {@link
@ -33,15 +32,17 @@ package java.io;
* respectively. * respectively.
* *
* <p> By default, line numbering begins at 0. This number increments at every * <p> By default, line numbering begins at 0. This number increments at every
* <a href="#lt">line terminator</a> as the data is read, and can be changed * <a href="#lt">line terminator</a> as the data is read, and at the end of the
* with a call to {@code setLineNumber(int)}. Note however, that * stream if the last character in the stream is not a line terminator. This
* {@code setLineNumber(int)} does not actually change the current position in * number can be changed with a call to {@code setLineNumber(int)}. Note
* the stream; it only changes the value that will be returned by * however, that {@code setLineNumber(int)} does not actually change the current
* position in the stream; it only changes the value that will be returned by
* {@code getLineNumber()}. * {@code getLineNumber()}.
* *
* <p> A line is considered to be <a id="lt">terminated</a> by any one of a * <p> A line is considered to be <a id="lt">terminated</a> by any one of a
* line feed ('\n'), a carriage return ('\r'), or a carriage return followed * line feed ('\n'), a carriage return ('\r'), or a carriage return followed
* immediately by a linefeed. * immediately by a linefeed, or any of the previous terminators followed by
* end of stream, or end of stream not preceded by another terminator.
* *
* @author Mark Reinhold * @author Mark Reinhold
* @since 1.1 * @since 1.1
@ -49,6 +50,15 @@ package java.io;
public class LineNumberReader extends BufferedReader { public class LineNumberReader extends BufferedReader {
/** Previous character types */
private static final int NONE = 0; // no previous character
private static final int CHAR = 1; // non-line terminator
private static final int EOL = 2; // line terminator
private static final int EOF = 3; // end-of-file
/** The previous character type */
private int prevChar = NONE;
/** The current line number */ /** The current line number */
private int lineNumber = 0; private int lineNumber = 0;
@ -111,8 +121,10 @@ public class LineNumberReader extends BufferedReader {
/** /**
* Read a single character. <a href="#lt">Line terminators</a> are * Read a single character. <a href="#lt">Line terminators</a> are
* compressed into single newline ('\n') characters. Whenever a line * compressed into single newline ('\n') characters. The current line
* terminator is read the current line number is incremented. * number is incremented whenever a line terminator is read, or when the
* end of the stream is reached and the last character in the stream is
* not a line terminator.
* *
* @return The character read, or -1 if the end of the stream has been * @return The character read, or -1 if the end of the stream has been
* reached * reached
@ -134,16 +146,27 @@ public class LineNumberReader extends BufferedReader {
skipLF = true; skipLF = true;
case '\n': /* Fall through */ case '\n': /* Fall through */
lineNumber++; lineNumber++;
prevChar = EOL;
return '\n'; return '\n';
case -1:
if (prevChar == CHAR)
lineNumber++;
prevChar = EOF;
break;
default:
prevChar = CHAR;
break;
} }
return c; return c;
} }
} }
/** /**
* Read characters into a portion of an array. Whenever a <a * Read characters into a portion of an array.
* href="#lt">line terminator</a> is read the current line number is * <a href="#lt">Line terminators</a> are compressed into single newline
* incremented. * ('\n') characters. The current line number is incremented whenever a
* line terminator is read, or when the end of the stream is reached and
* the last character in the stream is not a line terminator.
* *
* @param cbuf * @param cbuf
* Destination buffer * Destination buffer
@ -154,8 +177,8 @@ public class LineNumberReader extends BufferedReader {
* @param len * @param len
* Maximum number of characters to read * Maximum number of characters to read
* *
* @return The number of bytes read, or -1 if the end of the stream has * @return The number of characters read, or -1 if the end of the stream
* already been reached * has already been reached
* *
* @throws IOException * @throws IOException
* If an I/O error occurs * If an I/O error occurs
@ -167,6 +190,13 @@ public class LineNumberReader extends BufferedReader {
synchronized (lock) { synchronized (lock) {
int n = super.read(cbuf, off, len); int n = super.read(cbuf, off, len);
if (n == -1) {
if (prevChar == CHAR)
lineNumber++;
prevChar = EOF;
return -1;
}
for (int i = off; i < off + n; i++) { for (int i = off; i < off + n; i++) {
int c = cbuf[i]; int c = cbuf[i];
if (skipLF) { if (skipLF) {
@ -183,13 +213,28 @@ public class LineNumberReader extends BufferedReader {
} }
} }
if (n > 0) {
switch ((int)cbuf[off + n - 1]) {
case '\r':
case '\n': /* Fall through */
prevChar = EOL;
break;
default:
prevChar = CHAR;
break;
}
}
return n; return n;
} }
} }
/** /**
* Read a line of text. Whenever a <a href="#lt">line terminator</a> is * Read a line of text. <a href="#lt">Line terminators</a> are compressed
* read the current line number is incremented. * into single newline ('\n') characters. The current line number is
* incremented whenever a line terminator is read, or when the end of the
* stream is reached and the last character in the stream is not a line
* terminator.
* *
* @return A String containing the contents of the line, not including * @return A String containing the contents of the line, not including
* any <a href="#lt">line termination characters</a>, or * any <a href="#lt">line termination characters</a>, or
@ -200,10 +245,17 @@ public class LineNumberReader extends BufferedReader {
*/ */
public String readLine() throws IOException { public String readLine() throws IOException {
synchronized (lock) { synchronized (lock) {
String l = super.readLine(skipLF); boolean[] term = new boolean[1];
String l = super.readLine(skipLF, term);
skipLF = false; skipLF = false;
if (l != null) if (l != null) {
lineNumber++; lineNumber++;
prevChar = term[0] ? EOL : EOF;
} else { // l == null
if (prevChar == CHAR)
lineNumber++;
prevChar = EOF;
}
return l; return l;
} }
} }
@ -242,6 +294,9 @@ public class LineNumberReader extends BufferedReader {
break; break;
r -= nc; r -= nc;
} }
if (n - r > 0) {
prevChar = NONE;
}
return n - r; return n - r;
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1998, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -22,16 +22,24 @@
*/ */
/* @test /* @test
@bug 4074875 4063511 @bug 4074875 4063511 8235792
@summary Make sure LineNumberReader.read(char, int , int) will increase @summary Make sure LineNumberReader.read(char, int , int) will increase
the linenumber correctly. the linenumber correctly.
*/ */
import java.io.*; import java.io.IOException;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.util.function.Consumer;
public class Read { public class Read {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
testReadChars();
testEofs();
}
private static void testReadChars() throws Exception {
String s = "aaaa\nbbb\n"; String s = "aaaa\nbbb\n";
char[] buf = new char[5]; char[] buf = new char[5];
int n = 0; int n = 0;
@ -49,4 +57,49 @@ public class Read {
throw new Exception("Failed test: Expected line number: 2, got " throw new Exception("Failed test: Expected line number: 2, got "
+ line); + line);
} }
private static void testEofs() throws Exception {
String string = "first \n second";
Consumer<LineNumberReader> c = (LineNumberReader r) -> {
try {
while (r.read() != -1)
continue;
} catch (IOException e) {
throw new RuntimeException(e);
}
};
testEof(c, string, 2);
c = (LineNumberReader r) -> {
try {
char[] buf = new char[128];
while (r.read(buf) != -1)
continue;
} catch (IOException e) {
throw new RuntimeException(e);
}
};
testEof(c, string, 2);
c = (LineNumberReader r) -> {
try {
while (r.readLine() != null)
continue;
} catch (IOException e) {
throw new RuntimeException(e);
}
};
testEof(c, string, 2);
}
private static void testEof(Consumer<LineNumberReader> c, String s, int n)
throws Exception {
LineNumberReader r = new LineNumberReader(new StringReader(s));
c.accept(r);
int line;
if ((line = r.getLineNumber()) != n)
throw new Exception("Failed test: Expected line number: " + n +
" , got " + line);
}
} }