8342936: Enhance java.io.IO with parameter-less println() and readln()

Reviewed-by: asotona, jpai, naoto
This commit is contained in:
Jan Lahoda 2024-11-14 08:22:51 +00:00
parent b54bd824b5
commit c3776db498
14 changed files with 252 additions and 10 deletions

View File

@ -172,6 +172,19 @@ public sealed class Console implements Flushable permits ProxyingConsole {
throw newUnsupportedOperationException();
}
/**
* Terminates the current line in this console's output stream using
* {@link System#lineSeparator()} and then flushes the console.
*
* @return This console
*
* @since 24
*/
@PreviewFeature(feature = PreviewFeature.Feature.IMPLICIT_CLASSES)
public Console println() {
return println("");
}
/**
* Writes a string representation of the specified object to this console's
* output stream and then flushes the console.
@ -214,6 +227,24 @@ public sealed class Console implements Flushable permits ProxyingConsole {
throw newUnsupportedOperationException();
}
/**
* Reads a single line of text from this console.
*
* @throws IOError
* If an I/O error occurs.
*
* @return A string containing the line read from the console, not
* including any line-termination characters, or {@code null}
* if an end of stream has been reached without having read
* any characters.
*
* @since 24
*/
@PreviewFeature(feature = PreviewFeature.Feature.IMPLICIT_CLASSES)
public String readln() {
throw newUnsupportedOperationException();
}
/**
* Writes a formatted string to this console's output stream using
* the specified format string and arguments with the

View File

@ -63,6 +63,21 @@ public final class IO {
con().println(obj);
}
/**
* Terminates the current line on the system console and then flushes
* that console.
*
* <p> The effect is as if {@link Console#println() println()}
* had been called on {@code System.console()}.
*
* @throws IOError if {@code System.console()} returns {@code null},
* or if an I/O error occurs
* @since 24
*/
public static void println() {
con().println();
}
/**
* Writes a string representation of the specified object to the system
* console and then flushes that console.
@ -99,6 +114,24 @@ public final class IO {
return con().readln(prompt);
}
/**
* Reads a single line of text from the system console.
*
* <p> The effect is as if {@link Console#readln() readln()}
* had been called on {@code System.console()}.
*
* @return a string containing the line read from the system console, not
* including any line-termination characters. Returns {@code null} if an
* end of stream has been reached without having read any characters.
*
* @throws IOError if {@code System.console()} returns {@code null},
* or if an I/O error occurs
* @since 24
*/
public static String readln() {
return con().readln();
}
private static Console con() {
var con = System.console();
if (con != null) {

View File

@ -117,6 +117,18 @@ final class ProxyingConsole extends Console {
}
}
/**
* {@inheritDoc}
*
* @throws IOError {@inheritDoc}
*/
@Override
public String readln() {
synchronized (readLock) {
return delegate.readln();
}
}
/**
* {@inheritDoc}
*/

View File

@ -41,6 +41,7 @@ public interface JdkConsole {
JdkConsole println(Object obj);
JdkConsole print(Object obj);
String readln(String prompt);
String readln();
JdkConsole format(Locale locale, String format, Object ... args);
String readLine(Locale locale, String format, Object ... args);
String readLine();

View File

@ -90,6 +90,21 @@ public final class JdkConsoleImpl implements JdkConsole {
return line;
}
@Override
public String readln() {
String line = null;
synchronized(readLock) {
try {
char[] ca = readline(false);
if (ca != null)
line = new String(ca);
} catch (IOException x) {
throw new IOError(x);
}
}
return line;
}
@Override
public JdkConsole format(Locale locale, String format, Object ... args) {
formatter.format(locale, format, args).flush();

View File

@ -98,6 +98,11 @@ public class JdkConsoleProviderImpl implements JdkConsoleProvider {
return getDelegate(true).readln(prompt);
}
@Override
public String readln() {
return getDelegate(true).readln();
}
@Override
public JdkConsole format(Locale locale, String format, Object... args) {
JdkConsole delegate = getDelegate(false);
@ -224,6 +229,11 @@ public class JdkConsoleProviderImpl implements JdkConsoleProvider {
}
}
@Override
public String readln() {
return readLine();
}
@Override
public JdkConsole format(Locale locale, String format, Object ... args) {
writer().format(locale, format, args).flush();
@ -242,7 +252,12 @@ public class JdkConsoleProviderImpl implements JdkConsoleProvider {
@Override
public String readLine() {
return readLine(Locale.getDefault(Locale.Category.FORMAT), "");
try {
initJLineIfNeeded();
return jline.readLine();
} catch (EndOfFileException eofe) {
return null;
}
}
@Override

View File

@ -67,6 +67,10 @@ abstract class IOContext implements AutoCloseable {
throw new UserInterruptException("");
}
public String readUserLine() throws IOException {
throw new UserInterruptException("");
}
public Writer userOutput() {
throw new UnsupportedOperationException();
}

View File

@ -4112,6 +4112,15 @@ public class JShellTool implements MessageHandler {
}
}
@Override
public String readLine() throws IOError {
try {
return input.readUserLine();
} catch (IOException ex) {
throw new IOError(ex);
}
}
@Override
public char[] readPassword(String prompt) {
try {

View File

@ -28,6 +28,7 @@ import java.io.IOError;
import java.io.PrintWriter;
import java.io.Reader;
import java.nio.charset.Charset;
import jdk.internal.javac.PreviewFeature;
/**
* An interface providing functionality for {@link java.io.Console} in the user's snippet.
@ -75,6 +76,21 @@ public interface JShellConsole {
*/
public String readLine(String prompt) throws IOError;
/**
* Reads a single line of text from the console.
*
* @throws IOError
* If an I/O error occurs.
*
* @return A string containing the line read from the console, not
* including any line-termination characters, or {@code null}
* if an end of stream has been reached.
* @see java.io.Console#readLine()
* @since 24
*/
@PreviewFeature(feature=PreviewFeature.Feature.IMPLICIT_CLASSES)
public String readLine() throws IOError;
/**
* Provides a prompt, then reads a password or passphrase from
* the console with echoing disabled.

View File

@ -233,6 +233,16 @@ public class ConsoleImpl {
}
}
/**
* {@inheritDoc}
*
* @throws IOError {@inheritDoc}
*/
@Override
public String readln() {
return readLine();
}
/**
* {@inheritDoc}
*/
@ -269,7 +279,15 @@ public class ConsoleImpl {
*/
@Override
public String readLine() {
return readLine(Locale.getDefault(Locale.Category.FORMAT), "");
try {
return sendAndReceive(() -> {
remoteInput.write(Task.READ_LINE_NO_PROMPT.ordinal());
char[] line = readChars();
return new String(line);
});
} catch (IOException ex) {
throw new IOError(ex);
}
}
/**
@ -404,6 +422,12 @@ public class ConsoleImpl {
bp = 0;
}
}
case READ_LINE_NO_PROMPT -> {
String line = console.readLine();
char[] chars = line.toCharArray();
sendChars(sinkOutput, chars, 0, chars.length);
bp = 0;
}
case READ_PASSWORD -> {
char[] data = readCharsOrNull(1);
if (data != null) {
@ -478,6 +502,7 @@ public class ConsoleImpl {
FLUSH_OUTPUT,
READ_CHARS,
READ_LINE,
READ_LINE_NO_PROMPT,
READ_PASSWORD,
FLUSH_CONSOLE,
CHARSET,

View File

@ -21,6 +21,7 @@
* questions.
*/
import java.io.Writer;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
@ -33,6 +34,7 @@ import jdk.test.lib.process.ProcessTools;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
@ -48,7 +50,7 @@ import static org.junit.jupiter.api.Assertions.*;
/*
* @test
* @bug 8305457
* @bug 8305457 8342936
* @summary java.io.IO tests
* @library /test/lib
* @run junit IO
@ -131,22 +133,26 @@ public class IO {
var testSrc = System.getProperty("test.src", ".");
var command = new ArrayList<String>();
command.add(expect.toString());
command.add(Path.of(testSrc, "input.exp").toAbsolutePath().toString());
String expectInputName = PROMPT_NONE.equals(prompt) ? "input-no-prompt"
: "input";
command.add(Path.of(testSrc, expectInputName + ".exp").toAbsolutePath().toString());
command.add(System.getProperty("test.jdk") + "/bin/java");
command.add("--enable-preview");
if (console != null)
command.add("-Djdk.console=" + console);
command.add(Path.of(testSrc, "Input.java").toAbsolutePath().toString());
command.add(prompt == null ? "0" : "1");
command.add(prompt == null ? "0" : PROMPT_NONE.equals(prompt) ? "2" : "1");
command.add(String.valueOf(prompt));
OutputAnalyzer output = ProcessTools.executeProcess(command.toArray(new String[]{}));
output.reportDiagnosticSummary();
assertEquals(0, output.getExitValue());
}
private static final String PROMPT_NONE = "prompt-none";
public static Stream<Arguments> args() {
// cross product: consoles x prompts
return Stream.of(null, "gibberish").flatMap(console -> Stream.of(null, "?", "%s")
return Stream.of(null, "gibberish").flatMap(console -> Stream.of(null, "?", "%s", PROMPT_NONE)
.map(prompt -> new String[]{console, prompt}).map(Arguments::of));
}
}
@ -172,6 +178,33 @@ public class IO {
out.substring(out.length() / 2));
}
@Test //JDK-8342936
public void printlnNoParamsTest() throws Exception {
var file = Path.of("PrintlnNoParams.java");
try (Writer w = Files.newBufferedWriter(file)) {
w.write("""
void main() {
print("1 ");
print("2 ");
print("3 ");
println();
System.console().print("1 ");
System.console().print("2 ");
System.console().print("3 ");
System.console().println();
}
""");
}
var pb = ProcessTools.createTestJavaProcessBuilder("--enable-preview", file.toString());
OutputAnalyzer output = ProcessTools.executeProcess(pb);
assertEquals(0, output.getExitValue());
assertTrue(output.getStderr().isEmpty());
output.reportDiagnosticSummary();
String out = output.getStdout();
String nl = System.getProperty("line.separator");
assertEquals("1 2 3 " + nl + "1 2 3 " + nl, out);
}
@ParameterizedTest
@ValueSource(strings = {"println", "print", "input"})

View File

@ -28,9 +28,11 @@ import static java.io.IO.readln;
public class Input {
public static void main(String[] args) throws IOException {
if (args[0].equals("0"))
System.out.print(readln(null));
else
System.out.print(readln(args[1]));
switch (args[0]) {
case "0" -> System.out.print(readln(null));
case "1" -> System.out.print(readln(args[1]));
case "2" -> System.out.print(readln());
default -> throw new AssertionError("Unknown command: " + args[0]);
}
}
}

View File

@ -0,0 +1,30 @@
#
# Copyright (c) 2024, 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.
#
set prompt [lindex $argv $argc-1]
set stty_init "rows 24 cols 80"
set timeout -1
spawn {*}$argv
send "hello\r"
expect eof

View File

@ -25,6 +25,7 @@
* @test
* @bug 8298425
* @summary Verify behavior of System.console()
* @enablePreview
* @build KullaTesting TestingInputStream
* @run testng ConsoleTest
*/
@ -67,6 +68,13 @@ public class ConsoleTest extends KullaTesting {
}
};
assertEval("System.console().readLine(\"expected\")", "\"AB\"");
console = new ThrowingJShellConsole() {
@Override
public String readLine() throws IOError {
return "AB";
}
};
assertEval("System.console().readLine()", "\"AB\"");
console = new ThrowingJShellConsole() {
@Override
public char[] readPassword(String prompt) throws IOError {
@ -210,6 +218,10 @@ public class ConsoleTest extends KullaTesting {
return console.readLine(prompt);
}
@Override
public String readLine() throws IOError {
return console.readLine();
}
@Override
public char[] readPassword(String prompt) throws IOError {
return console.readPassword(prompt);
}
@ -240,6 +252,10 @@ public class ConsoleTest extends KullaTesting {
throw new IllegalStateException("Not expected!");
}
@Override
public String readLine() throws IOError {
throw new IllegalStateException("Not expected!");
}
@Override
public char[] readPassword(String prompt) throws IOError {
throw new IllegalStateException("Not expected!");
}