--- old/src/java.base/share/classes/java/io/BufferedReader.java 2020-03-19 07:28:50.000000000 -0700 +++ new/src/java.base/share/classes/java/io/BufferedReader.java 2020-03-19 07:28:49.000000000 -0700 @@ -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. * * This code is free software; you can redistribute it and/or modify it @@ -302,6 +302,8 @@ * (EOF). * * @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 * any line-termination characters, or null if the end of the @@ -311,13 +313,14 @@ * * @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; int startChar; synchronized (lock) { ensureOpen(); boolean omitLF = ignoreLF || skipLF; + if (term != null) term[0] = false; bufferLoop: for (;;) { @@ -344,6 +347,7 @@ for (i = nextChar; i < nChars; i++) { c = cb[i]; if ((c == '\n') || (c == '\r')) { + if (term != null) term[0] = true; eol = true; break charLoop; } @@ -389,7 +393,7 @@ * @see java.nio.file.Files#readAllLines */ public String readLine() throws IOException { - return readLine(false); + return readLine(false, null); } /** --- old/src/java.base/share/classes/java/io/LineNumberReader.java 2020-03-19 07:28:51.000000000 -0700 +++ new/src/java.base/share/classes/java/io/LineNumberReader.java 2020-03-19 07:28:50.000000000 -0700 @@ -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. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,6 @@ package java.io; - /** * A buffered character-input stream that keeps track of line numbers. This * class defines methods {@link #setLineNumber(int)} and {@link @@ -33,15 +32,17 @@ * respectively. * *

By default, line numbering begins at 0. This number increments at every - * line terminator as the data is read, and can be changed - * with a call to {@code setLineNumber(int)}. Note 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 + * line terminator as the data is read, and at the end of the + * stream if the last character in the stream is not a line terminator. This + * number can be changed with a call to {@code setLineNumber(int)}. Note + * 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()}. * *

A line is considered to be terminated by any one of a * 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 * @since 1.1 @@ -49,6 +50,15 @@ 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 */ private int lineNumber = 0; @@ -111,8 +121,10 @@ /** * Read a single character. Line terminators are - * compressed into single newline ('\n') characters. Whenever a line - * terminator is read the current line number is incremented. + * compressed 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 The character read, or -1 if the end of the stream has been * reached @@ -134,16 +146,26 @@ skipLF = true; case '\n': /* Fall through */ lineNumber++; + prevChar = EOL; return '\n'; + case -1: + if (prevChar == CHAR) + lineNumber++; + prevChar = EOF; + break; + default: + prevChar = CHAR; + break; } return c; } } /** - * Read characters into a portion of an array. Whenever a line terminator is read the current line number is - * incremented. + * Read characters into a portion of an array. 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 * Destination buffer @@ -167,6 +189,13 @@ synchronized (lock) { 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++) { int c = cbuf[i]; if (skipLF) { @@ -183,13 +212,26 @@ } } + if (n > 0) { + switch ((int)cbuf[off + n - 1]) { + case '\r': + case '\n': /* Fall through */ + prevChar = EOL; + break; + default: + prevChar = CHAR; + break; + } + } + return n; } } /** - * Read a line of text. Whenever a line terminator is - * read the current line number is incremented. + * Read a line of text. 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 * any line termination characters, or @@ -200,10 +242,17 @@ */ public String readLine() throws IOException { synchronized (lock) { - String l = super.readLine(skipLF); + boolean[] term = new boolean[1]; + String l = super.readLine(skipLF, term); skipLF = false; - if (l != null) + if (l != null) { lineNumber++; + prevChar = term[0] ? EOL : EOF; + } else { // l == null + if (prevChar == CHAR) + lineNumber++; + prevChar = EOF; + } return l; } } @@ -242,6 +291,9 @@ break; r -= nc; } + if (n - r > 0) { + prevChar = NONE; + } return n - r; } } --- old/test/jdk/java/io/LineNumberReader/Read.java 2020-03-19 07:28:51.000000000 -0700 +++ new/test/jdk/java/io/LineNumberReader/Read.java 2020-03-19 07:28:51.000000000 -0700 @@ -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. * * This code is free software; you can redistribute it and/or modify it @@ -22,16 +22,24 @@ */ /* @test - @bug 4074875 4063511 + @bug 4074875 4063511 8235792 @summary Make sure LineNumberReader.read(char, int , int) will increase 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 static void main(String[] args) throws Exception { + testReadChars(); + testEofs(); + } + + private static void testReadChars() throws Exception { String s = "aaaa\nbbb\n"; char[] buf = new char[5]; int n = 0; @@ -49,4 +57,49 @@ throw new Exception("Failed test: Expected line number: 2, got " + line); } + + private static void testEofs() throws Exception { + String string = "first \n second"; + + Consumer 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 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); + } }