diff --git a/src/java.base/share/classes/java/io/Console.java b/src/java.base/share/classes/java/io/Console.java index 8ae4fc00773..d8ba0439d47 100644 --- a/src/java.base/share/classes/java/io/Console.java +++ b/src/java.base/share/classes/java/io/Console.java @@ -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 diff --git a/src/java.base/share/classes/java/io/IO.java b/src/java.base/share/classes/java/io/IO.java index 7485f87f03f..a49a51041fc 100644 --- a/src/java.base/share/classes/java/io/IO.java +++ b/src/java.base/share/classes/java/io/IO.java @@ -63,6 +63,21 @@ public final class IO { con().println(obj); } + /** + * Terminates the current line on the system console and then flushes + * that console. + * + *

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. + * + *

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) { diff --git a/src/java.base/share/classes/java/io/ProxyingConsole.java b/src/java.base/share/classes/java/io/ProxyingConsole.java index 1babceb665f..cc5cd926264 100644 --- a/src/java.base/share/classes/java/io/ProxyingConsole.java +++ b/src/java.base/share/classes/java/io/ProxyingConsole.java @@ -117,6 +117,18 @@ final class ProxyingConsole extends Console { } } + /** + * {@inheritDoc} + * + * @throws IOError {@inheritDoc} + */ + @Override + public String readln() { + synchronized (readLock) { + return delegate.readln(); + } + } + /** * {@inheritDoc} */ diff --git a/src/java.base/share/classes/jdk/internal/io/JdkConsole.java b/src/java.base/share/classes/jdk/internal/io/JdkConsole.java index 6c911ed6fed..08bd840de37 100644 --- a/src/java.base/share/classes/jdk/internal/io/JdkConsole.java +++ b/src/java.base/share/classes/jdk/internal/io/JdkConsole.java @@ -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(); diff --git a/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java b/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java index a1086b245d1..3c0afd2005c 100644 --- a/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java +++ b/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java @@ -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(); diff --git a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java index f40b9662625..11de96b7fc3 100644 --- a/src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java +++ b/src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java @@ -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 diff --git a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/IOContext.java b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/IOContext.java index 339a3005d7d..e22d927911c 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/IOContext.java +++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/IOContext.java @@ -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(); } diff --git a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java index 2d06ffc529d..d2d2ed10e63 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java +++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java @@ -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 { diff --git a/src/jdk.jshell/share/classes/jdk/jshell/JShellConsole.java b/src/jdk.jshell/share/classes/jdk/jshell/JShellConsole.java index fe73d6965cb..014766f9ff7 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/JShellConsole.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/JShellConsole.java @@ -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. diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/impl/ConsoleImpl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/impl/ConsoleImpl.java index b85b8c9ea0f..876f61ec856 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/execution/impl/ConsoleImpl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/impl/ConsoleImpl.java @@ -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, diff --git a/test/jdk/java/io/IO/IO.java b/test/jdk/java/io/IO/IO.java index 328c189fb2f..e4da1742030 100644 --- a/test/jdk/java/io/IO/IO.java +++ b/test/jdk/java/io/IO/IO.java @@ -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(); 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 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"}) diff --git a/test/jdk/java/io/IO/Input.java b/test/jdk/java/io/IO/Input.java index 1bc03c8bb8a..1a62fb77166 100644 --- a/test/jdk/java/io/IO/Input.java +++ b/test/jdk/java/io/IO/Input.java @@ -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]); + } } } diff --git a/test/jdk/java/io/IO/input-no-prompt.exp b/test/jdk/java/io/IO/input-no-prompt.exp new file mode 100644 index 00000000000..20cd481912b --- /dev/null +++ b/test/jdk/java/io/IO/input-no-prompt.exp @@ -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 diff --git a/test/langtools/jdk/jshell/ConsoleTest.java b/test/langtools/jdk/jshell/ConsoleTest.java index 29961a180ba..4aedc061939 100644 --- a/test/langtools/jdk/jshell/ConsoleTest.java +++ b/test/langtools/jdk/jshell/ConsoleTest.java @@ -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!"); }