diff --git a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java index f4ca58d6af4..c92e6e11f1f 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java +++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java @@ -1001,6 +1001,11 @@ class ConsoleIOContext extends IOContext { return doReadUserLine(prompt, null); } + @Override + public String readUserLine() throws IOException { + return readUserLine(""); + } + private synchronized String doReadUserLine(String prompt, Character mask) throws IOException { History prevHistory = in.getHistory(); boolean prevDisableCr = Display.DISABLE_CR; 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 e22d927911c..7a1234628de 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 @@ -64,6 +64,8 @@ abstract class IOContext implements AutoCloseable { } public String readUserLine(String prompt) throws IOException { + userOutput().write(prompt); + userOutput().flush(); throw new UserInterruptException(""); } @@ -76,6 +78,8 @@ abstract class IOContext implements AutoCloseable { } public char[] readPassword(String prompt) throws IOException { + userOutput().write(prompt); + userOutput().flush(); throw new UserInterruptException(""); } 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 d2d2ed10e63..1adcb472b77 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 @@ -35,6 +35,7 @@ import java.io.IOError; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.PrintWriter; import java.io.Reader; @@ -1192,7 +1193,7 @@ public class JShellTool implements MessageHandler { //where private void startUpRun(String start) { - try (IOContext suin = new ScannerIOContext(new StringReader(start))) { + try (IOContext suin = new ScannerIOContext(new StringReader(start), userout)) { while (run(suin)) { if (!live) { resetState(); @@ -3125,7 +3126,7 @@ public class JShellTool implements MessageHandler { throw new FileNotFoundException(filename); } } - try (var scannerIOContext = new ScannerIOContext(scanner)) { + try (var scannerIOContext = new ScannerIOContext(scanner, userout)) { run(scannerIOContext); } return true; @@ -3288,8 +3289,10 @@ public class JShellTool implements MessageHandler { resetState(); } if (history != null) { - run(new ReloadIOContext(history.iterable(), - echo ? cmdout : null)); + try (ReloadIOContext ctx = new ReloadIOContext(history.iterable(), + echo ? cmdout : null, userout)) { + run(ctx); + } } return true; } @@ -4107,6 +4110,8 @@ public class JShellTool implements MessageHandler { public String readLine(String prompt) { try { return input.readUserLine(prompt); + } catch (UserInterruptException ex) { + return null; } catch (IOException ex) { throw new IOError(ex); } @@ -4125,6 +4130,8 @@ public class JShellTool implements MessageHandler { public char[] readPassword(String prompt) { try { return input.readPassword(prompt); + } catch (UserInterruptException ex) { + return null; } catch (IOException ex) { throw new IOError(ex); } @@ -4144,6 +4151,12 @@ public class JShellTool implements MessageHandler { abstract class NonInteractiveIOContext extends IOContext { + private final Writer userOutput; + + public NonInteractiveIOContext(PrintStream userOutput) { + this.userOutput = new OutputStreamWriter(userOutput); + } + @Override public boolean interactiveOutput() { return false; @@ -4178,17 +4191,33 @@ abstract class NonInteractiveIOContext extends IOContext { @Override public void replaceLastHistoryEntry(String source) { } + + @Override + public Writer userOutput() { + return userOutput; + } + + @Override + public void close() { + try { + userOutput.flush(); + } catch (IOException _) { + //ignore + } + } + } class ScannerIOContext extends NonInteractiveIOContext { private final Scanner scannerIn; - ScannerIOContext(Scanner scannerIn) { + ScannerIOContext(Scanner scannerIn, PrintStream userOutput) { + super(userOutput); this.scannerIn = scannerIn; } - ScannerIOContext(Reader rdr) throws FileNotFoundException { - this(new Scanner(rdr)); + ScannerIOContext(Reader rdr, PrintStream userOutput) throws FileNotFoundException { + this(new Scanner(rdr), userOutput); } @Override @@ -4202,6 +4231,7 @@ class ScannerIOContext extends NonInteractiveIOContext { @Override public void close() { + super.close(); scannerIn.close(); } @@ -4215,7 +4245,8 @@ class ReloadIOContext extends NonInteractiveIOContext { private final Iterator it; private final PrintStream echoStream; - ReloadIOContext(Iterable history, PrintStream echoStream) { + ReloadIOContext(Iterable history, PrintStream echoStream, PrintStream userOutput) { + super(userOutput); this.it = history.iterator(); this.echoStream = echoStream; } 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 876f61ec856..a697a37141e 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 @@ -117,6 +117,9 @@ public class ConsoleImpl { private char[] readChars() throws IOException { int actualLen = readInt(); + if (actualLen == (-1)) { + return null; + } char[] result = new char[actualLen]; for (int i = 0; i < actualLen; i++) { result[i] = (char) ((remoteOutput.read() << 8) | @@ -267,6 +270,9 @@ public class ConsoleImpl { remoteInput.write(Task.READ_LINE.ordinal()); sendChars(chars, 0, chars.length); char[] line = readChars(); + if (line == null) { + return null; + } return new String(line); }); } catch (IOException ex) { @@ -417,8 +423,12 @@ public class ConsoleImpl { char[] data = readCharsOrNull(1); if (data != null) { String line = console.readLine(new String(data)); - char[] chars = line.toCharArray(); - sendChars(sinkOutput, chars, 0, chars.length); + if (line == null) { + sendInt(sinkOutput, -1); + } else { + char[] chars = line.toCharArray(); + sendChars(sinkOutput, chars, 0, chars.length); + } bp = 0; } } @@ -432,7 +442,11 @@ public class ConsoleImpl { char[] data = readCharsOrNull(1); if (data != null) { char[] chars = console.readPassword(new String(data)); - sendChars(sinkOutput, chars, 0, chars.length); + if (chars == null) { + sendInt(sinkOutput, -1); + } else { + sendChars(sinkOutput, chars, 0, chars.length); + } bp = 0; } } diff --git a/src/jdk.jshell/share/classes/jdk/jshell/tool/resources/PREVIEW_DEFAULT.jsh b/src/jdk.jshell/share/classes/jdk/jshell/tool/resources/PREVIEW_DEFAULT.jsh index 4644ac86386..975d1215535 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/tool/resources/PREVIEW_DEFAULT.jsh +++ b/src/jdk.jshell/share/classes/jdk/jshell/tool/resources/PREVIEW_DEFAULT.jsh @@ -1 +1,2 @@ import module java.base; +import static java.io.IO.*; diff --git a/test/langtools/jdk/jshell/ConsoleToolTest.java b/test/langtools/jdk/jshell/ConsoleToolTest.java index 8fa85a3ec27..0af85a61862 100644 --- a/test/langtools/jdk/jshell/ConsoleToolTest.java +++ b/test/langtools/jdk/jshell/ConsoleToolTest.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8331535 + * @bug 8331535 8341631 * @summary Test the JShell tool Console handling * @modules jdk.internal.le/jdk.internal.org.jline.reader * jdk.jshell/jdk.internal.jshell.tool:+open @@ -56,6 +56,40 @@ public class ConsoleToolTest extends ReplToolTesting { ); } + @Test //JDK-8341631 + public void testIO() { + test(new String[] {"--enable-preview"}, + a -> {assertCommandWithOutputAndTerminal(a, + "java.io.IO.readln(\"%%s\");\ninput", //newline automatically appended + "$1 ==> \"input\"", + """ + \u0005java.io.IO.readln(\"%%s\"); + %%sinput + """);}, + a -> {assertCommandWithOutputAndTerminal(a, + "java.io.IO.readln();\ninput!", //newline automatically appended + "$2 ==> \"input!\"", + """ + \u0005java.io.IO.readln(); + input! + """);}, + a -> {assertCommandWithOutputAndTerminal(a, + "java.io.IO.println(\"Hello, World!\");", + "", + """ + \u0005java.io.IO.println(\"Hello, World!\"); + Hello, World! + """);}, + a -> {assertCommandWithOutputAndTerminal(a, + "java.io.IO.println();", + "", + """ + \u0005java.io.IO.println(); + + """);} + ); + } + void assertCommandWithOutputAndTerminal(boolean a, String command, String out, String terminalOut) { assertCommand(a, command, out, null, null, null, null, terminalOut); } diff --git a/test/langtools/jdk/jshell/StartOptionTest.java b/test/langtools/jdk/jshell/StartOptionTest.java index 60926e69843..b84b454480e 100644 --- a/test/langtools/jdk/jshell/StartOptionTest.java +++ b/test/langtools/jdk/jshell/StartOptionTest.java @@ -22,7 +22,7 @@ */ /* - * @test 8151754 8080883 8160089 8170162 8166581 8172102 8171343 8178023 8186708 8179856 8185840 8190383 + * @test 8151754 8080883 8160089 8170162 8166581 8172102 8171343 8178023 8186708 8179856 8185840 8190383 8341631 * @summary Testing startExCe-up options. * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.main @@ -366,14 +366,49 @@ public class StartOptionTest { } public void testPreviewEnabled() { - String fn = writeToFile("System.out.println(\"prefix\");\n" + - "System.out.println(MethodHandle.class.getName());\n" + - "System.out.println(\"suffix\");\n" + - "/exit\n"); + String fn = writeToFile( + """ + System.out.println(\"prefix\"); + System.out.println(MethodHandle.class.getName()); + System.out.println(\"suffix\"); + /exit + """); startCheckUserOutput(s -> assertEquals(s, "prefix\nsuffix\n"), fn); startCheckUserOutput(s -> assertEquals(s, "prefix\njava.lang.invoke.MethodHandle\nsuffix\n"), "--enable-preview", fn); + //JDK-8341631: + String fn2 = writeToFile( + """ + System.out.println(\"prefix\"); + IO.println(\"test\"); + System.out.println(\"suffix\"); + /exit + """); + startCheckUserOutput(s -> assertEquals(s, "prefix\nsuffix\n"), + fn2); + startCheckUserOutput(s -> assertEquals(s, "prefix\ntest\nsuffix\n"), + "--enable-preview", fn2); + } + public void testInput() { + //readLine(String): + String readLinePrompt = writeToFile( + """ + var v = System.console().readLine("prompt: "); + System.out.println(v); + /exit + """); + startCheckUserOutput(s -> assertEquals(s, "prompt: null\n"), + readLinePrompt); + //readPassword(String): + String readPasswordPrompt = writeToFile( + """ + var v = System.console().readPassword("prompt: "); + System.out.println(java.util.Arrays.toString(v)); + /exit + """); + startCheckUserOutput(s -> assertEquals(s, "prompt: null\n"), + readPasswordPrompt); } @AfterMethod