From 3113ad8337e515105a3dd90a43eabef5f44f9a7e Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Wed, 14 May 2025 11:27:00 -0700 Subject: [PATCH 1/6] initial commit --- .../share/classes/java/io/Console.java | 14 ++- .../jdk/internal/io/JdkConsoleImpl.java | 17 ++-- .../jdk/internal/io/JdkConsoleProvider.java | 5 +- .../org/jline/JdkConsoleProviderImpl.java | 16 ++-- .../jshell/execution/impl/ConsoleImpl.java | 2 +- test/jdk/java/io/Console/CharsetTest.java | 3 +- .../java/io/Console/StdinEncodingTest.java | 79 +++++++++++++++++ test/jdk/java/io/Console/csp/module-info.java | 26 ++++++ .../csp/provider/ZCharsetProvider.java | 88 +++++++++++++++++++ test/jdk/java/io/Console/script.exp | 4 +- test/jdk/java/io/Console/stdinEncoding.exp | 34 +++++++ 11 files changed, 263 insertions(+), 25 deletions(-) create mode 100644 test/jdk/java/io/Console/StdinEncodingTest.java create mode 100644 test/jdk/java/io/Console/csp/module-info.java create mode 100644 test/jdk/java/io/Console/csp/provider/ZCharsetProvider.java create mode 100644 test/jdk/java/io/Console/stdinEncoding.exp diff --git a/src/java.base/share/classes/java/io/Console.java b/src/java.base/share/classes/java/io/Console.java index aaa3ba8dd270b..2b804cad7e60a 100644 --- a/src/java.base/share/classes/java/io/Console.java +++ b/src/java.base/share/classes/java/io/Console.java @@ -59,6 +59,12 @@ * on the objects returned by {@link #reader()} and {@link #writer()} may * block in multithreaded scenarios. *

+ * Read and write operations use the {@code Charset} returned by the + * {@link #charset()} method, unless {@link System##stdin.encoding + * stdin.encoding} differs from {@link System##stdout.encoding + * stdout.encoding}, in which case read operations use the {@code Charset} + * designated by {@code stdin.encoding}. + *

* Operations that format strings are locale sensitive, using either the * specified {@code Locale}, or the * {@link Locale##default_locale default format Locale} to produce localized @@ -549,7 +555,9 @@ private static UnsupportedOperationException newUnsupportedOperationException() } private static final boolean istty = istty(); - static final Charset CHARSET = + static final Charset STDIN_CHARSET = + Charset.forName(System.getProperty("stdin.encoding"), UTF_8.INSTANCE); + static final Charset STDOUT_CHARSET = Charset.forName(System.getProperty("stdout.encoding"), UTF_8.INSTANCE); private static final Console cons = instantiateConsole(); static { @@ -579,7 +587,7 @@ private static Console instantiateConsole() { for (var jcp : ServiceLoader.load(ModuleLayer.boot(), JdkConsoleProvider.class)) { if (consModName.equals(jcp.getClass().getModule().getName())) { - var jc = jcp.console(istty, CHARSET); + var jc = jcp.console(istty, STDIN_CHARSET, STDOUT_CHARSET); if (jc != null) { c = new ProxyingConsole(jc); } @@ -591,7 +599,7 @@ private static Console instantiateConsole() { // If not found, default to built-in Console if (istty && c == null) { - c = new ProxyingConsole(new JdkConsoleImpl(CHARSET)); + c = new ProxyingConsole(new JdkConsoleImpl(STDIN_CHARSET, STDOUT_CHARSET)); } return c; 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 008ed68f56f35..ec94d4ec4d67f 100644 --- a/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java +++ b/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java @@ -191,10 +191,11 @@ public void flush() { @Override public Charset charset() { - return charset; + return outCharset; } - private final Charset charset; + private final Charset inCharset; + private final Charset outCharset; private final Object readLock; private final Object writeLock; // Must not block while holding this. It is used in the shutdown hook. @@ -364,16 +365,18 @@ public int read(char[] cbuf, int offset, int length) } } - public JdkConsoleImpl(Charset charset) { - Objects.requireNonNull(charset); - this.charset = charset; + public JdkConsoleImpl(Charset inCharset, Charset outCharset) { + Objects.requireNonNull(inCharset); + Objects.requireNonNull(outCharset); + this.inCharset = inCharset; + this.outCharset = outCharset; readLock = new Object(); writeLock = new Object(); restoreEchoLock = new Object(); out = StreamEncoder.forOutputStreamWriter( new FileOutputStream(FileDescriptor.out), writeLock, - charset); + outCharset); pw = new PrintWriter(out, true) { public void close() { } @@ -382,7 +385,7 @@ public void close() { reader = new LineReader(StreamDecoder.forInputStreamReader( new FileInputStream(FileDescriptor.in), readLock, - charset)); + inCharset)); rcb = new char[1024]; } } diff --git a/src/java.base/share/classes/jdk/internal/io/JdkConsoleProvider.java b/src/java.base/share/classes/jdk/internal/io/JdkConsoleProvider.java index 4ac196d67ff9f..f3cc67e85a0e7 100644 --- a/src/java.base/share/classes/jdk/internal/io/JdkConsoleProvider.java +++ b/src/java.base/share/classes/jdk/internal/io/JdkConsoleProvider.java @@ -38,7 +38,8 @@ public interface JdkConsoleProvider { /** * {@return the Console instance, or {@code null} if not available} * @param isTTY indicates if the jvm is attached to a terminal - * @param charset charset of the platform console + * @param inCharset Standard input charset of the platform console + * @param outCharset Standard output charset of the platform console */ - JdkConsole console(boolean isTTY, Charset charset); + JdkConsole console(boolean isTTY, Charset inCharset, Charset outCharset); } 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 03a9da22771f9..2c29cb0b6e5de 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 @@ -49,18 +49,18 @@ public class JdkConsoleProviderImpl implements JdkConsoleProvider { * {@inheritDoc} */ @Override - public JdkConsole console(boolean isTTY, Charset charset) { - return new LazyDelegatingJdkConsoleImpl(charset); + public JdkConsole console(boolean isTTY, Charset inCharset, Charset outCharset) { + return new LazyDelegatingJdkConsoleImpl(inCharset, outCharset); } private static class LazyDelegatingJdkConsoleImpl implements JdkConsole { - private final Charset charset; + private final Charset outCharset; private volatile boolean jlineInitialized; private volatile JdkConsole delegate; - public LazyDelegatingJdkConsoleImpl(Charset charset) { - this.charset = charset; - this.delegate = new jdk.internal.io.JdkConsoleImpl(charset); + public LazyDelegatingJdkConsoleImpl(Charset inCharset, Charset outCharset) { + this.outCharset = outCharset; + this.delegate = new jdk.internal.io.JdkConsoleImpl(inCharset, outCharset); } @Override @@ -130,7 +130,7 @@ public void flush() { @Override public Charset charset() { - return charset; + return outCharset; } private void flushOldDelegateIfNeeded(JdkConsole oldDelegate) { @@ -157,7 +157,7 @@ private synchronized JdkConsole initializeJLineDelegate() { } try { - Terminal terminal = TerminalBuilder.builder().encoding(charset) + Terminal terminal = TerminalBuilder.builder().encoding(outCharset) .exec(false) .nativeSignals(false) .systemOutput(SystemOutput.SysOut) 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 0282b62da4cac..5f4defc8afdb6 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 @@ -64,7 +64,7 @@ public ConsoleProviderImpl() { private static RemoteConsole console; @Override - public JdkConsole console(boolean isTTY, Charset charset) { + public JdkConsole console(boolean isTTY, Charset inCharset, Charset outCharset) { synchronized (ConsoleProviderImpl.class) { if (remoteOutput != null && remoteInput != null) { return console = new RemoteConsole(remoteOutput, remoteInput); diff --git a/test/jdk/java/io/Console/CharsetTest.java b/test/jdk/java/io/Console/CharsetTest.java index 8bc23fa58f8bb..b55db31db88aa 100644 --- a/test/jdk/java/io/Console/CharsetTest.java +++ b/test/jdk/java/io/Console/CharsetTest.java @@ -50,8 +50,7 @@ public static void main(String... args) throws Throwable { // check "expect" command availability var expect = Paths.get("/usr/bin/expect"); if (!Files.exists(expect) || !Files.isExecutable(expect)) { - System.out.println("'expect' command not found. Test ignored."); - return; + throw new jtreg.SkippedException("'expect' command not found. Test ignored."); } // invoking "expect" command diff --git a/test/jdk/java/io/Console/StdinEncodingTest.java b/test/jdk/java/io/Console/StdinEncodingTest.java new file mode 100644 index 0000000000000..3347e4109ed0f --- /dev/null +++ b/test/jdk/java/io/Console/StdinEncodingTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025, 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. + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @test + * @bug 8356985 + * @summary Tests if "stdin.encoding" is reflected for reading + * the console. + * @library /test/lib + * @build csp/* + * @run junit StdinEncodingTest + */ +public class StdinEncodingTest { + + @Test + @EnabledOnOs({OS.LINUX, OS.MAC}) + public void testStdinEncoding() throws Throwable { + // check "expect" command availability + var expect = Paths.get("/usr/bin/expect"); + if (!Files.exists(expect) || !Files.isExecutable(expect)) { + Assumptions.abort("'" + expect + "' not found"); + } + + // invoking "expect" command + var testSrc = System.getProperty("test.src", "."); + var testClasses = System.getProperty("test.classes", "."); + var jdkDir = System.getProperty("test.jdk"); + OutputAnalyzer output = ProcessTools.executeProcess( + "expect", + "-n", + testSrc + "/stdinEncoding.exp", + jdkDir + "/bin/java", + "--module-path", + testClasses + "/modules", + "-Dstdin.encoding=Z", + "StdinEncodingTest"); + output.reportDiagnosticSummary(); + var eval = output.getExitValue(); + if (eval != 0) { + throw new RuntimeException("Test failed. Exit value from 'expect' command: " + eval); + } + } + + public static void main(String... args) throws Throwable { + System.out.println(System.console().readLine()); + } +} diff --git a/test/jdk/java/io/Console/csp/module-info.java b/test/jdk/java/io/Console/csp/module-info.java new file mode 100644 index 0000000000000..2305fd5675150 --- /dev/null +++ b/test/jdk/java/io/Console/csp/module-info.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025, 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. + */ + +module csp { + provides java.nio.charset.spi.CharsetProvider with provider.ZCharsetProvider; +} diff --git a/test/jdk/java/io/Console/csp/provider/ZCharsetProvider.java b/test/jdk/java/io/Console/csp/provider/ZCharsetProvider.java new file mode 100644 index 0000000000000..bf6168aa7bcec --- /dev/null +++ b/test/jdk/java/io/Console/csp/provider/ZCharsetProvider.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2025, 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. + */ +package provider; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.spi.CharsetProvider; +import java.util.Collections; +import java.util.Iterator; + +// A test charset provider that decodes every input byte into Z +public class ZCharsetProvider extends CharsetProvider { + + @Override + public Iterator charsets() { + return Collections.singleton(new ZCharsetProvider.ZCharset()).iterator(); + } + + @Override + public Charset charsetForName(String charsetName) { + if (charsetName.equals("Z")) { + return new ZCharsetProvider.ZCharset(); + } else { + return null; + } + } + + public static class ZCharset extends Charset { + + public ZCharset() { + super("Z", null); + } + + @Override + public boolean contains(Charset cs) { + return false; + } + + @Override + public CharsetDecoder newDecoder() { + return new ZCharsetDecoder(this, 1, 1); + } + + @Override + public CharsetEncoder newEncoder() { + return null; + } + } + + private static class ZCharsetDecoder extends CharsetDecoder { + public ZCharsetDecoder(Charset cs, float averageCharsPerByte, float maxCharsPerByte) { + super(cs, averageCharsPerByte, maxCharsPerByte); + } + + @Override + protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) { + while (in.remaining() > 0) { + in.get(); + out.put('Z'); + } + return CoderResult.UNDERFLOW; + } + } +} diff --git a/test/jdk/java/io/Console/script.exp b/test/jdk/java/io/Console/script.exp index 3c97a3c4143e1..c416990cbe943 100644 --- a/test/jdk/java/io/Console/script.exp +++ b/test/jdk/java/io/Console/script.exp @@ -1,5 +1,5 @@ # -# Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2021, 2025, 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 @@ -27,6 +27,6 @@ set expected [lrange $argv 2 2] set args [lrange $argv 3 end] regexp {([a-zA-Z_]*).([a-zA-Z0-9\-]*)} $locale dummy lang_region encoding -eval spawn $java -Dsun.stdout.encoding=$encoding -classpath $args CharsetTest +eval spawn $java -Dstdout.encoding=$encoding -classpath $args CharsetTest expect $expected expect eof diff --git a/test/jdk/java/io/Console/stdinEncoding.exp b/test/jdk/java/io/Console/stdinEncoding.exp new file mode 100644 index 0000000000000..d7ca18701e07d --- /dev/null +++ b/test/jdk/java/io/Console/stdinEncoding.exp @@ -0,0 +1,34 @@ +# +# Copyright (c) 2025, 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 java [lrange $argv 0 end] + +eval spawn $java +send "abc\r" + +# expect 4 'Z's converted with ZCharset with the input 'abc\r' +expect "ZZZZ" { + expect eof + exit 0 +} +exit 1 From f2eb1ad8a72d8062cd6573bee1a2b142648d3574 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Fri, 16 May 2025 16:49:43 -0700 Subject: [PATCH 2/6] Tests read* method variations --- .../java/io/Console/StdinEncodingTest.java | 24 ++++++++++++---- test/jdk/java/io/Console/csp/module-info.java | 2 +- ...Provider.java => MockCharsetProvider.java} | 28 ++++++++++--------- test/jdk/java/io/Console/stdinEncoding.exp | 16 +++++++++-- 4 files changed, 49 insertions(+), 21 deletions(-) rename test/jdk/java/io/Console/csp/provider/{ZCharsetProvider.java => MockCharsetProvider.java} (72%) diff --git a/test/jdk/java/io/Console/StdinEncodingTest.java b/test/jdk/java/io/Console/StdinEncodingTest.java index 3347e4109ed0f..28d6835b29309 100644 --- a/test/jdk/java/io/Console/StdinEncodingTest.java +++ b/test/jdk/java/io/Console/StdinEncodingTest.java @@ -24,6 +24,7 @@ import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; +import java.io.BufferedReader; import java.nio.file.Files; import java.nio.file.Paths; @@ -64,16 +65,29 @@ public void testStdinEncoding() throws Throwable { jdkDir + "/bin/java", "--module-path", testClasses + "/modules", - "-Dstdin.encoding=Z", + "-Dstdin.encoding=Mock", // <- gist of this test "StdinEncodingTest"); output.reportDiagnosticSummary(); var eval = output.getExitValue(); - if (eval != 0) { - throw new RuntimeException("Test failed. Exit value from 'expect' command: " + eval); - } + assertEquals(0, eval, "Test failed. Exit value from 'expect' command: " + eval); } public static void main(String... args) throws Throwable { - System.out.println(System.console().readLine()); + // check stdin.encoding + if (!"Mock".equals(System.getProperty("stdin.encoding"))) { + throw new RuntimeException("Mock charset was not set in stdin.encoding"); + } + var con = System.console(); + + // Console.readLine() + System.out.print(con.readLine()); + + // Console.readPassword() + System.out.print(String.valueOf(con.readPassword())); + + // Console.reader() + try (var br = new BufferedReader(con.reader())) { + System.out.print(br.readLine()); + } } } diff --git a/test/jdk/java/io/Console/csp/module-info.java b/test/jdk/java/io/Console/csp/module-info.java index 2305fd5675150..462835e2ea52a 100644 --- a/test/jdk/java/io/Console/csp/module-info.java +++ b/test/jdk/java/io/Console/csp/module-info.java @@ -22,5 +22,5 @@ */ module csp { - provides java.nio.charset.spi.CharsetProvider with provider.ZCharsetProvider; + provides java.nio.charset.spi.CharsetProvider with provider.MockCharsetProvider; } diff --git a/test/jdk/java/io/Console/csp/provider/ZCharsetProvider.java b/test/jdk/java/io/Console/csp/provider/MockCharsetProvider.java similarity index 72% rename from test/jdk/java/io/Console/csp/provider/ZCharsetProvider.java rename to test/jdk/java/io/Console/csp/provider/MockCharsetProvider.java index bf6168aa7bcec..e0155cf7c0c6c 100644 --- a/test/jdk/java/io/Console/csp/provider/ZCharsetProvider.java +++ b/test/jdk/java/io/Console/csp/provider/MockCharsetProvider.java @@ -32,27 +32,27 @@ import java.util.Collections; import java.util.Iterator; -// A test charset provider that decodes every input byte into Z -public class ZCharsetProvider extends CharsetProvider { +// A test charset provider that decodes every input byte into its uppercase +public class MockCharsetProvider extends CharsetProvider { @Override public Iterator charsets() { - return Collections.singleton(new ZCharsetProvider.ZCharset()).iterator(); + return Collections.singleton(new MockCharsetProvider.MockCharset()).iterator(); } @Override public Charset charsetForName(String charsetName) { - if (charsetName.equals("Z")) { - return new ZCharsetProvider.ZCharset(); + if (charsetName.equals("Mock")) { + return new MockCharsetProvider.MockCharset(); } else { return null; } } - public static class ZCharset extends Charset { + public static class MockCharset extends Charset { - public ZCharset() { - super("Z", null); + public MockCharset() { + super("Mock", null); } @Override @@ -62,7 +62,7 @@ public boolean contains(Charset cs) { @Override public CharsetDecoder newDecoder() { - return new ZCharsetDecoder(this, 1, 1); + return new MockCharsetDecoder(this, 1, 1); } @Override @@ -71,16 +71,18 @@ public CharsetEncoder newEncoder() { } } - private static class ZCharsetDecoder extends CharsetDecoder { - public ZCharsetDecoder(Charset cs, float averageCharsPerByte, float maxCharsPerByte) { + private static class MockCharsetDecoder extends CharsetDecoder { + public MockCharsetDecoder(Charset cs, float averageCharsPerByte, float maxCharsPerByte) { super(cs, averageCharsPerByte, maxCharsPerByte); } @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) { while (in.remaining() > 0) { - in.get(); - out.put('Z'); + char c = (char)in.get(); + if (c != '\n') { + out.put(Character.toUpperCase(c)); + } } return CoderResult.UNDERFLOW; } diff --git a/test/jdk/java/io/Console/stdinEncoding.exp b/test/jdk/java/io/Console/stdinEncoding.exp index d7ca18701e07d..59746a5e570c8 100644 --- a/test/jdk/java/io/Console/stdinEncoding.exp +++ b/test/jdk/java/io/Console/stdinEncoding.exp @@ -24,11 +24,23 @@ set java [lrange $argv 0 end] eval spawn $java + +# Console::readLine() send "abc\r" -# expect 4 'Z's converted with ZCharset with the input 'abc\r' -expect "ZZZZ" { +# Console::readPassword() +send "def\r" + +# Console::reader() +send "ghi\r" + +# check the output +expect "ABCDEFGHI" { expect eof + send_error "Mocked output received" exit 0 } + +# fail +send_error "Mocked output not received." exit 1 From ec42065444385def6d73c01e2303eddc409e5a0e Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Tue, 20 May 2025 13:48:51 -0700 Subject: [PATCH 3/6] Reflects review comments --- .../share/classes/java/io/Console.java | 4 +-- test/jdk/java/io/Console/CharsetTest.java | 2 +- .../java/io/Console/StdinEncodingTest.java | 15 ++++---- test/jdk/java/io/Console/csp/module-info.java | 2 +- ...r.java => UppercasingCharsetProvider.java} | 25 ++++++------- test/jdk/java/io/Console/stdinEncoding.exp | 35 ++++++++++++++----- 6 files changed, 49 insertions(+), 34 deletions(-) rename test/jdk/java/io/Console/csp/provider/{MockCharsetProvider.java => UppercasingCharsetProvider.java} (75%) diff --git a/src/java.base/share/classes/java/io/Console.java b/src/java.base/share/classes/java/io/Console.java index 2b804cad7e60a..cd246570437df 100644 --- a/src/java.base/share/classes/java/io/Console.java +++ b/src/java.base/share/classes/java/io/Console.java @@ -555,9 +555,9 @@ private static UnsupportedOperationException newUnsupportedOperationException() } private static final boolean istty = istty(); - static final Charset STDIN_CHARSET = + private static final Charset STDIN_CHARSET = Charset.forName(System.getProperty("stdin.encoding"), UTF_8.INSTANCE); - static final Charset STDOUT_CHARSET = + private static final Charset STDOUT_CHARSET = Charset.forName(System.getProperty("stdout.encoding"), UTF_8.INSTANCE); private static final Console cons = instantiateConsole(); static { diff --git a/test/jdk/java/io/Console/CharsetTest.java b/test/jdk/java/io/Console/CharsetTest.java index b55db31db88aa..f68c91eb21dce 100644 --- a/test/jdk/java/io/Console/CharsetTest.java +++ b/test/jdk/java/io/Console/CharsetTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, 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 diff --git a/test/jdk/java/io/Console/StdinEncodingTest.java b/test/jdk/java/io/Console/StdinEncodingTest.java index 28d6835b29309..f30cffb81a452 100644 --- a/test/jdk/java/io/Console/StdinEncodingTest.java +++ b/test/jdk/java/io/Console/StdinEncodingTest.java @@ -30,15 +30,14 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledOnOs; -import org.junit.jupiter.api.condition.OS; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * @test * @bug 8356985 * @summary Tests if "stdin.encoding" is reflected for reading * the console. + * @requires (os.family == "linux" | os.family == "mac") * @library /test/lib * @build csp/* * @run junit StdinEncodingTest @@ -46,7 +45,6 @@ public class StdinEncodingTest { @Test - @EnabledOnOs({OS.LINUX, OS.MAC}) public void testStdinEncoding() throws Throwable { // check "expect" command availability var expect = Paths.get("/usr/bin/expect"); @@ -65,7 +63,7 @@ public void testStdinEncoding() throws Throwable { jdkDir + "/bin/java", "--module-path", testClasses + "/modules", - "-Dstdin.encoding=Mock", // <- gist of this test + "-Dstdin.encoding=Uppercasing", // <- gist of this test "StdinEncodingTest"); output.reportDiagnosticSummary(); var eval = output.getExitValue(); @@ -74,8 +72,8 @@ public void testStdinEncoding() throws Throwable { public static void main(String... args) throws Throwable { // check stdin.encoding - if (!"Mock".equals(System.getProperty("stdin.encoding"))) { - throw new RuntimeException("Mock charset was not set in stdin.encoding"); + if (!"Uppercasing".equals(System.getProperty("stdin.encoding"))) { + throw new RuntimeException("Uppercasing charset was not set in stdin.encoding"); } var con = System.console(); @@ -89,5 +87,8 @@ public static void main(String... args) throws Throwable { try (var br = new BufferedReader(con.reader())) { System.out.print(br.readLine()); } + + // Wait till the test receives the result + con.readLine(); } } diff --git a/test/jdk/java/io/Console/csp/module-info.java b/test/jdk/java/io/Console/csp/module-info.java index 462835e2ea52a..9178c3ea631cd 100644 --- a/test/jdk/java/io/Console/csp/module-info.java +++ b/test/jdk/java/io/Console/csp/module-info.java @@ -22,5 +22,5 @@ */ module csp { - provides java.nio.charset.spi.CharsetProvider with provider.MockCharsetProvider; + provides java.nio.charset.spi.CharsetProvider with provider.UppercasingCharsetProvider; } diff --git a/test/jdk/java/io/Console/csp/provider/MockCharsetProvider.java b/test/jdk/java/io/Console/csp/provider/UppercasingCharsetProvider.java similarity index 75% rename from test/jdk/java/io/Console/csp/provider/MockCharsetProvider.java rename to test/jdk/java/io/Console/csp/provider/UppercasingCharsetProvider.java index e0155cf7c0c6c..ee1142de416a7 100644 --- a/test/jdk/java/io/Console/csp/provider/MockCharsetProvider.java +++ b/test/jdk/java/io/Console/csp/provider/UppercasingCharsetProvider.java @@ -33,26 +33,26 @@ import java.util.Iterator; // A test charset provider that decodes every input byte into its uppercase -public class MockCharsetProvider extends CharsetProvider { +public class UppercasingCharsetProvider extends CharsetProvider { @Override public Iterator charsets() { - return Collections.singleton(new MockCharsetProvider.MockCharset()).iterator(); + return Collections.singleton(new UppercasingCharsetProvider.UppercasingCharset()).iterator(); } @Override public Charset charsetForName(String charsetName) { - if (charsetName.equals("Mock")) { - return new MockCharsetProvider.MockCharset(); + if (charsetName.equals("Uppercasing")) { + return new UppercasingCharsetProvider.UppercasingCharset(); } else { return null; } } - public static class MockCharset extends Charset { + public static class UppercasingCharset extends Charset { - public MockCharset() { - super("Mock", null); + public UppercasingCharset() { + super("Uppercasing", null); } @Override @@ -62,7 +62,7 @@ public boolean contains(Charset cs) { @Override public CharsetDecoder newDecoder() { - return new MockCharsetDecoder(this, 1, 1); + return new UppercasingCharsetDecoder(this, 1, 1); } @Override @@ -71,18 +71,15 @@ public CharsetEncoder newEncoder() { } } - private static class MockCharsetDecoder extends CharsetDecoder { - public MockCharsetDecoder(Charset cs, float averageCharsPerByte, float maxCharsPerByte) { + private static class UppercasingCharsetDecoder extends CharsetDecoder { + public UppercasingCharsetDecoder(Charset cs, float averageCharsPerByte, float maxCharsPerByte) { super(cs, averageCharsPerByte, maxCharsPerByte); } @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) { while (in.remaining() > 0) { - char c = (char)in.get(); - if (c != '\n') { - out.put(Character.toUpperCase(c)); - } + out.put(Character.toUpperCase((char)in.get())); } return CoderResult.UNDERFLOW; } diff --git a/test/jdk/java/io/Console/stdinEncoding.exp b/test/jdk/java/io/Console/stdinEncoding.exp index 59746a5e570c8..23950f195f1bb 100644 --- a/test/jdk/java/io/Console/stdinEncoding.exp +++ b/test/jdk/java/io/Console/stdinEncoding.exp @@ -27,20 +27,37 @@ eval spawn $java # Console::readLine() send "abc\r" +expect { + "ABC" { send_error "Console::readLine() received\n" } + timeout { + send_error "Error: Console::readLine() not received\n" + exit 1 + } +} # Console::readPassword() send "def\r" +expect { + "DEF" { send_error "Console::readPassword() received\n" } + timeout { + send_error "Error: Console::readPassword() not received\n" + exit 1 + } +} # Console::reader() send "ghi\r" - -# check the output -expect "ABCDEFGHI" { - expect eof - send_error "Mocked output received" - exit 0 +expect { + "GHI" { send_error "Console::reader() received\n" } + timeout { + send_error "Error: Console::reader() not received\n" + exit 1 + } } -# fail -send_error "Mocked output not received." -exit 1 +# should receive eof +send "\r" +expect eof + +# success +exit 0 From dd3707686c3000da62dedb537d7a436508cafa92 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Wed, 21 May 2025 09:46:59 -0700 Subject: [PATCH 4/6] Reflecting further comments --- .../share/classes/java/io/Console.java | 4 ++++ .../java/io/Console/StdinEncodingTest.java | 24 +++++++++---------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/java.base/share/classes/java/io/Console.java b/src/java.base/share/classes/java/io/Console.java index cd246570437df..42cf86e305fa0 100644 --- a/src/java.base/share/classes/java/io/Console.java +++ b/src/java.base/share/classes/java/io/Console.java @@ -523,6 +523,10 @@ public void flush() { * which defaults to the one based on {@link System##stdout.encoding stdout.encoding}. * It may not necessarily be the same as the default charset returned from * {@link java.nio.charset.Charset#defaultCharset() Charset.defaultCharset()}. + * If {@link System##stdin.encoding stdin.encoding} differs from + * {@link System##stdout.encoding stdout.encoding}, read operations use the + * {@code Charset} designated by {@code stdin.encoding}, instead of the returned + * {@code Charset}. * * @return a {@link java.nio.charset.Charset Charset} object used for the * {@code Console} diff --git a/test/jdk/java/io/Console/StdinEncodingTest.java b/test/jdk/java/io/Console/StdinEncodingTest.java index f30cffb81a452..1f2ea225f960a 100644 --- a/test/jdk/java/io/Console/StdinEncodingTest.java +++ b/test/jdk/java/io/Console/StdinEncodingTest.java @@ -21,13 +21,14 @@ * questions. */ -import jdk.test.lib.process.OutputAnalyzer; -import jdk.test.lib.process.ProcessTools; - import java.io.BufferedReader; import java.nio.file.Files; import java.nio.file.Paths; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import static jdk.test.lib.Utils.*; + import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -36,7 +37,8 @@ * @test * @bug 8356985 * @summary Tests if "stdin.encoding" is reflected for reading - * the console. + * the console. "expect" command in Windows/Cygwin does + * not work as expected. Ignoring tests on Windows. * @requires (os.family == "linux" | os.family == "mac") * @library /test/lib * @build csp/* @@ -48,21 +50,17 @@ public class StdinEncodingTest { public void testStdinEncoding() throws Throwable { // check "expect" command availability var expect = Paths.get("/usr/bin/expect"); - if (!Files.exists(expect) || !Files.isExecutable(expect)) { - Assumptions.abort("'" + expect + "' not found"); - } + Assumptions.assumeTrue(Files.exists(expect) && Files.isExecutable(expect), + "'" + expect + "' not found"); // invoking "expect" command - var testSrc = System.getProperty("test.src", "."); - var testClasses = System.getProperty("test.classes", "."); - var jdkDir = System.getProperty("test.jdk"); OutputAnalyzer output = ProcessTools.executeProcess( "expect", "-n", - testSrc + "/stdinEncoding.exp", - jdkDir + "/bin/java", + TEST_SRC + "/stdinEncoding.exp", + TEST_JDK + "/bin/java", "--module-path", - testClasses + "/modules", + TEST_CLASSES + "/modules", "-Dstdin.encoding=Uppercasing", // <- gist of this test "StdinEncodingTest"); output.reportDiagnosticSummary(); From ce1c80352d05ec9c9c7452097745c30fba5d1382 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Wed, 21 May 2025 12:23:26 -0700 Subject: [PATCH 5/6] CharsetTest clean-up --- test/jdk/java/io/Console/CharsetTest.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/jdk/java/io/Console/CharsetTest.java b/test/jdk/java/io/Console/CharsetTest.java index f68c91eb21dce..c8c756def810c 100644 --- a/test/jdk/java/io/Console/CharsetTest.java +++ b/test/jdk/java/io/Console/CharsetTest.java @@ -27,10 +27,11 @@ import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; +import static jdk.test.lib.Utils.*; /** * @test - * @bug 8264208 8265918 + * @bug 8264208 8265918 8356985 * @summary Tests Console.charset() method. "expect" command in Windows/Cygwin * does not work as expected. Ignoring tests on Windows. * @requires (os.family == "linux") | (os.family == "mac") @@ -54,17 +55,14 @@ public static void main(String... args) throws Throwable { } // invoking "expect" command - var testSrc = System.getProperty("test.src", "."); - var testClasses = System.getProperty("test.classes", "."); - var jdkDir = System.getProperty("test.jdk"); OutputAnalyzer output = ProcessTools.executeProcess( "expect", "-n", - testSrc + "/script.exp", - jdkDir + "/bin/java", + TEST_SRC + "/script.exp", + TEST_JDK + "/bin/java", args[0], args[1], - testClasses); + TEST_CLASSES); output.reportDiagnosticSummary(); var eval = output.getExitValue(); if (eval != 0) { From 75fa0a890e7297ad9e93cc5e3fea5ee3fe3d8e68 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Thu, 22 May 2025 10:42:12 -0700 Subject: [PATCH 6/6] Reflects wording change suggestions --- .../share/classes/java/io/Console.java | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/java.base/share/classes/java/io/Console.java b/src/java.base/share/classes/java/io/Console.java index 42cf86e305fa0..cedb6124a31ee 100644 --- a/src/java.base/share/classes/java/io/Console.java +++ b/src/java.base/share/classes/java/io/Console.java @@ -59,11 +59,12 @@ * on the objects returned by {@link #reader()} and {@link #writer()} may * block in multithreaded scenarios. *

- * Read and write operations use the {@code Charset} returned by the - * {@link #charset()} method, unless {@link System##stdin.encoding - * stdin.encoding} differs from {@link System##stdout.encoding - * stdout.encoding}, in which case read operations use the {@code Charset} - * designated by {@code stdin.encoding}. + * Read and write operations use the {@code Charset}s specified by + * {@link System##stdin.encoding stdin.encoding} and {@link + * System##stdout.encoding stdout.encoding}, respectively. The + * {@code Charset} used for write operations can also be retrieved using + * the {@link #charset()} method. Since {@code Console} is intended for + * interactive use on a terminal, these charsets are typically the same. *

* Operations that format strings are locale sensitive, using either the * specified {@code Locale}, or the @@ -515,21 +516,15 @@ public void flush() { } /** - * Returns the {@link java.nio.charset.Charset Charset} object used for - * the {@code Console}. + * {@return the {@link java.nio.charset.Charset Charset} object used for + * the write operations on this {@code Console}} *

- * The returned charset is used for interpreting the input and output source - * (e.g., keyboard and/or display) specified by the host environment or user, - * which defaults to the one based on {@link System##stdout.encoding stdout.encoding}. - * It may not necessarily be the same as the default charset returned from + * The returned charset is used for encoding the data that is sent to + * the output (e.g., display), specified by the host environment or user. + * It defaults to the one based on {@link System##stdout.encoding stdout.encoding}, + * and may not necessarily be the same as the default charset returned from * {@link java.nio.charset.Charset#defaultCharset() Charset.defaultCharset()}. - * If {@link System##stdin.encoding stdin.encoding} differs from - * {@link System##stdout.encoding stdout.encoding}, read operations use the - * {@code Charset} designated by {@code stdin.encoding}, instead of the returned - * {@code Charset}. * - * @return a {@link java.nio.charset.Charset Charset} object used for the - * {@code Console} * @since 17 */ public Charset charset() {