Skip to content

Commit b2a61a9

Browse files
committed
8356985: Use "stdin.encoding" in Console's read*() methods
Reviewed-by: jlu, smarks, alanb, vyazici
1 parent 8949c07 commit b2a61a9

File tree

11 files changed

+317
-41
lines changed

11 files changed

+317
-41
lines changed

src/java.base/share/classes/java/io/Console.java

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@
5959
* on the objects returned by {@link #reader()} and {@link #writer()} may
6060
* block in multithreaded scenarios.
6161
* <p>
62+
* Read and write operations use the {@code Charset}s specified by
63+
* {@link System##stdin.encoding stdin.encoding} and {@link
64+
* System##stdout.encoding stdout.encoding}, respectively. The
65+
* {@code Charset} used for write operations can also be retrieved using
66+
* the {@link #charset()} method. Since {@code Console} is intended for
67+
* interactive use on a terminal, these charsets are typically the same.
68+
* <p>
6269
* Operations that format strings are locale sensitive, using either the
6370
* specified {@code Locale}, or the
6471
* {@link Locale##default_locale default format Locale} to produce localized
@@ -509,17 +516,15 @@ public void flush() {
509516
}
510517

511518
/**
512-
* Returns the {@link java.nio.charset.Charset Charset} object used for
513-
* the {@code Console}.
519+
* {@return the {@link java.nio.charset.Charset Charset} object used for
520+
* the write operations on this {@code Console}}
514521
* <p>
515-
* The returned charset is used for interpreting the input and output source
516-
* (e.g., keyboard and/or display) specified by the host environment or user,
517-
* which defaults to the one based on {@link System##stdout.encoding stdout.encoding}.
518-
* It may not necessarily be the same as the default charset returned from
522+
* The returned charset is used for encoding the data that is sent to
523+
* the output (e.g., display), specified by the host environment or user.
524+
* It defaults to the one based on {@link System##stdout.encoding stdout.encoding},
525+
* and may not necessarily be the same as the default charset returned from
519526
* {@link java.nio.charset.Charset#defaultCharset() Charset.defaultCharset()}.
520527
*
521-
* @return a {@link java.nio.charset.Charset Charset} object used for the
522-
* {@code Console}
523528
* @since 17
524529
*/
525530
public Charset charset() {
@@ -549,7 +554,9 @@ private static UnsupportedOperationException newUnsupportedOperationException()
549554
}
550555

551556
private static final boolean istty = istty();
552-
static final Charset CHARSET =
557+
private static final Charset STDIN_CHARSET =
558+
Charset.forName(System.getProperty("stdin.encoding"), UTF_8.INSTANCE);
559+
private static final Charset STDOUT_CHARSET =
553560
Charset.forName(System.getProperty("stdout.encoding"), UTF_8.INSTANCE);
554561
private static final Console cons = instantiateConsole();
555562
static {
@@ -579,7 +586,7 @@ private static Console instantiateConsole() {
579586

580587
for (var jcp : ServiceLoader.load(ModuleLayer.boot(), JdkConsoleProvider.class)) {
581588
if (consModName.equals(jcp.getClass().getModule().getName())) {
582-
var jc = jcp.console(istty, CHARSET);
589+
var jc = jcp.console(istty, STDIN_CHARSET, STDOUT_CHARSET);
583590
if (jc != null) {
584591
c = new ProxyingConsole(jc);
585592
}
@@ -591,7 +598,7 @@ private static Console instantiateConsole() {
591598

592599
// If not found, default to built-in Console
593600
if (istty && c == null) {
594-
c = new ProxyingConsole(new JdkConsoleImpl(CHARSET));
601+
c = new ProxyingConsole(new JdkConsoleImpl(STDIN_CHARSET, STDOUT_CHARSET));
595602
}
596603

597604
return c;

src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,11 @@ public void flush() {
191191

192192
@Override
193193
public Charset charset() {
194-
return charset;
194+
return outCharset;
195195
}
196196

197-
private final Charset charset;
197+
private final Charset inCharset;
198+
private final Charset outCharset;
198199
private final Object readLock;
199200
private final Object writeLock;
200201
// 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)
364365
}
365366
}
366367

367-
public JdkConsoleImpl(Charset charset) {
368-
Objects.requireNonNull(charset);
369-
this.charset = charset;
368+
public JdkConsoleImpl(Charset inCharset, Charset outCharset) {
369+
Objects.requireNonNull(inCharset);
370+
Objects.requireNonNull(outCharset);
371+
this.inCharset = inCharset;
372+
this.outCharset = outCharset;
370373
readLock = new Object();
371374
writeLock = new Object();
372375
restoreEchoLock = new Object();
373376
out = StreamEncoder.forOutputStreamWriter(
374377
new FileOutputStream(FileDescriptor.out),
375378
writeLock,
376-
charset);
379+
outCharset);
377380
pw = new PrintWriter(out, true) {
378381
public void close() {
379382
}
@@ -382,7 +385,7 @@ public void close() {
382385
reader = new LineReader(StreamDecoder.forInputStreamReader(
383386
new FileInputStream(FileDescriptor.in),
384387
readLock,
385-
charset));
388+
inCharset));
386389
rcb = new char[1024];
387390
}
388391
}

src/java.base/share/classes/jdk/internal/io/JdkConsoleProvider.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ public interface JdkConsoleProvider {
3838
/**
3939
* {@return the Console instance, or {@code null} if not available}
4040
* @param isTTY indicates if the jvm is attached to a terminal
41-
* @param charset charset of the platform console
41+
* @param inCharset Standard input charset of the platform console
42+
* @param outCharset Standard output charset of the platform console
4243
*/
43-
JdkConsole console(boolean isTTY, Charset charset);
44+
JdkConsole console(boolean isTTY, Charset inCharset, Charset outCharset);
4445
}

src/jdk.internal.le/share/classes/jdk/internal/org/jline/JdkConsoleProviderImpl.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,18 @@ public class JdkConsoleProviderImpl implements JdkConsoleProvider {
4949
* {@inheritDoc}
5050
*/
5151
@Override
52-
public JdkConsole console(boolean isTTY, Charset charset) {
53-
return new LazyDelegatingJdkConsoleImpl(charset);
52+
public JdkConsole console(boolean isTTY, Charset inCharset, Charset outCharset) {
53+
return new LazyDelegatingJdkConsoleImpl(inCharset, outCharset);
5454
}
5555

5656
private static class LazyDelegatingJdkConsoleImpl implements JdkConsole {
57-
private final Charset charset;
57+
private final Charset outCharset;
5858
private volatile boolean jlineInitialized;
5959
private volatile JdkConsole delegate;
6060

61-
public LazyDelegatingJdkConsoleImpl(Charset charset) {
62-
this.charset = charset;
63-
this.delegate = new jdk.internal.io.JdkConsoleImpl(charset);
61+
public LazyDelegatingJdkConsoleImpl(Charset inCharset, Charset outCharset) {
62+
this.outCharset = outCharset;
63+
this.delegate = new jdk.internal.io.JdkConsoleImpl(inCharset, outCharset);
6464
}
6565

6666
@Override
@@ -130,7 +130,7 @@ public void flush() {
130130

131131
@Override
132132
public Charset charset() {
133-
return charset;
133+
return outCharset;
134134
}
135135

136136
private void flushOldDelegateIfNeeded(JdkConsole oldDelegate) {
@@ -157,7 +157,7 @@ private synchronized JdkConsole initializeJLineDelegate() {
157157
}
158158

159159
try {
160-
Terminal terminal = TerminalBuilder.builder().encoding(charset)
160+
Terminal terminal = TerminalBuilder.builder().encoding(outCharset)
161161
.exec(false)
162162
.nativeSignals(false)
163163
.systemOutput(SystemOutput.SysOut)

src/jdk.jshell/share/classes/jdk/jshell/execution/impl/ConsoleImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public ConsoleProviderImpl() {
6464
private static RemoteConsole console;
6565

6666
@Override
67-
public JdkConsole console(boolean isTTY, Charset charset) {
67+
public JdkConsole console(boolean isTTY, Charset inCharset, Charset outCharset) {
6868
synchronized (ConsoleProviderImpl.class) {
6969
if (remoteOutput != null && remoteInput != null) {
7070
return console = new RemoteConsole(remoteOutput, remoteInput);

test/jdk/java/io/Console/CharsetTest.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -27,10 +27,11 @@
2727

2828
import jdk.test.lib.process.OutputAnalyzer;
2929
import jdk.test.lib.process.ProcessTools;
30+
import static jdk.test.lib.Utils.*;
3031

3132
/**
3233
* @test
33-
* @bug 8264208 8265918
34+
* @bug 8264208 8265918 8356985
3435
* @summary Tests Console.charset() method. "expect" command in Windows/Cygwin
3536
* does not work as expected. Ignoring tests on Windows.
3637
* @requires (os.family == "linux") | (os.family == "mac")
@@ -50,22 +51,18 @@ public static void main(String... args) throws Throwable {
5051
// check "expect" command availability
5152
var expect = Paths.get("/usr/bin/expect");
5253
if (!Files.exists(expect) || !Files.isExecutable(expect)) {
53-
System.out.println("'expect' command not found. Test ignored.");
54-
return;
54+
throw new jtreg.SkippedException("'expect' command not found. Test ignored.");
5555
}
5656

5757
// invoking "expect" command
58-
var testSrc = System.getProperty("test.src", ".");
59-
var testClasses = System.getProperty("test.classes", ".");
60-
var jdkDir = System.getProperty("test.jdk");
6158
OutputAnalyzer output = ProcessTools.executeProcess(
6259
"expect",
6360
"-n",
64-
testSrc + "/script.exp",
65-
jdkDir + "/bin/java",
61+
TEST_SRC + "/script.exp",
62+
TEST_JDK + "/bin/java",
6663
args[0],
6764
args[1],
68-
testClasses);
65+
TEST_CLASSES);
6966
output.reportDiagnosticSummary();
7067
var eval = output.getExitValue();
7168
if (eval != 0) {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
import java.io.BufferedReader;
25+
import java.nio.file.Files;
26+
import java.nio.file.Paths;
27+
28+
import jdk.test.lib.process.OutputAnalyzer;
29+
import jdk.test.lib.process.ProcessTools;
30+
import static jdk.test.lib.Utils.*;
31+
32+
import org.junit.jupiter.api.Assumptions;
33+
import org.junit.jupiter.api.Test;
34+
import static org.junit.jupiter.api.Assertions.assertEquals;
35+
36+
/**
37+
* @test
38+
* @bug 8356985
39+
* @summary Tests if "stdin.encoding" is reflected for reading
40+
* the console. "expect" command in Windows/Cygwin does
41+
* not work as expected. Ignoring tests on Windows.
42+
* @requires (os.family == "linux" | os.family == "mac")
43+
* @library /test/lib
44+
* @build csp/*
45+
* @run junit StdinEncodingTest
46+
*/
47+
public class StdinEncodingTest {
48+
49+
@Test
50+
public void testStdinEncoding() throws Throwable {
51+
// check "expect" command availability
52+
var expect = Paths.get("/usr/bin/expect");
53+
Assumptions.assumeTrue(Files.exists(expect) && Files.isExecutable(expect),
54+
"'" + expect + "' not found");
55+
56+
// invoking "expect" command
57+
OutputAnalyzer output = ProcessTools.executeProcess(
58+
"expect",
59+
"-n",
60+
TEST_SRC + "/stdinEncoding.exp",
61+
TEST_JDK + "/bin/java",
62+
"--module-path",
63+
TEST_CLASSES + "/modules",
64+
"-Dstdin.encoding=Uppercasing", // <- gist of this test
65+
"StdinEncodingTest");
66+
output.reportDiagnosticSummary();
67+
var eval = output.getExitValue();
68+
assertEquals(0, eval, "Test failed. Exit value from 'expect' command: " + eval);
69+
}
70+
71+
public static void main(String... args) throws Throwable {
72+
// check stdin.encoding
73+
if (!"Uppercasing".equals(System.getProperty("stdin.encoding"))) {
74+
throw new RuntimeException("Uppercasing charset was not set in stdin.encoding");
75+
}
76+
var con = System.console();
77+
78+
// Console.readLine()
79+
System.out.print(con.readLine());
80+
81+
// Console.readPassword()
82+
System.out.print(String.valueOf(con.readPassword()));
83+
84+
// Console.reader()
85+
try (var br = new BufferedReader(con.reader())) {
86+
System.out.print(br.readLine());
87+
}
88+
89+
// Wait till the test receives the result
90+
con.readLine();
91+
}
92+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
module csp {
25+
provides java.nio.charset.spi.CharsetProvider with provider.UppercasingCharsetProvider;
26+
}

0 commit comments

Comments
 (0)