From bae224793812cb0a0aa67e399062498d3b13fdb3 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Thu, 20 Jul 2023 16:11:13 +0000 Subject: [PATCH] 8308591: JLine as the default Console provider Reviewed-by: alanb --- .../share/classes/java/io/Console.java | 19 ++++++++++- .../classes/java/io/ProxyingConsole.java | 34 ++++++++++++++----- .../jdk/internal/io/JdkConsoleProvider.java | 2 +- .../org/jline/JdkConsoleProviderImpl.java | 20 +++++++++-- .../java/io/Console/ModuleSelectionTest.java | 2 +- 5 files changed, 62 insertions(+), 15 deletions(-) diff --git a/src/java.base/share/classes/java/io/Console.java b/src/java.base/share/classes/java/io/Console.java index d6673861f69..2e864484323 100644 --- a/src/java.base/share/classes/java/io/Console.java +++ b/src/java.base/share/classes/java/io/Console.java @@ -344,16 +344,33 @@ public sealed class Console implements Flushable permits ProxyingConsole { throw newUnsupportedOperationException(); } + /** + * {@return {@code true} if the {@code Console} instance is a terminal} + *

+ * This method returns {@code true} if the console device, associated with the current + * Java virtual machine, is a terminal, typically an interactive command line + * connected to a keyboard and display. + * + * @implNote The default implementation returns the value equivalent to calling + * {@code isatty(stdin/stdout)} on POSIX platforms, or whether standard in/out file + * descriptors are character devices or not on Windows. + * + * @since 22 + */ + public boolean isTerminal() { + return istty; + } + private static UnsupportedOperationException newUnsupportedOperationException() { return new UnsupportedOperationException( "Console class itself does not provide implementation"); } private static native String encoding(); + private static final boolean istty = istty(); static final Charset CHARSET; static { Charset cs = null; - boolean istty = istty(); if (istty) { String csname = encoding(); diff --git a/src/java.base/share/classes/java/io/ProxyingConsole.java b/src/java.base/share/classes/java/io/ProxyingConsole.java index 71068530300..0e8a6e83301 100644 --- a/src/java.base/share/classes/java/io/ProxyingConsole.java +++ b/src/java.base/share/classes/java/io/ProxyingConsole.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2023, 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 @@ -34,17 +34,13 @@ import jdk.internal.io.JdkConsole; */ final class ProxyingConsole extends Console { private final JdkConsole delegate; - private final Object readLock; - private final Object writeLock; - private final Reader reader; - private final PrintWriter printWriter; + private final Object readLock = new Object(); + private final Object writeLock = new Object(); + private volatile Reader reader; + private volatile PrintWriter printWriter; ProxyingConsole(JdkConsole delegate) { this.delegate = delegate; - readLock = new Object(); - writeLock = new Object(); - reader = new WrappingReader(delegate.reader(), readLock); - printWriter = new WrappingWriter(delegate.writer(), writeLock); } /** @@ -52,6 +48,16 @@ final class ProxyingConsole extends Console { */ @Override public PrintWriter writer() { + PrintWriter printWriter = this.printWriter; + if (printWriter == null) { + synchronized (this) { + printWriter = this.printWriter; + if (printWriter == null) { + printWriter = new WrappingWriter(delegate.writer(), writeLock); + this.printWriter = printWriter; + } + } + } return printWriter; } @@ -60,6 +66,16 @@ final class ProxyingConsole extends Console { */ @Override public Reader reader() { + Reader reader = this.reader; + if (reader == null) { + synchronized (this) { + reader = this.reader; + if (reader == null) { + reader = new WrappingReader(delegate.reader(), readLock); + this.reader = reader; + } + } + } return reader; } 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 ae1992012de..26e59c84e23 100644 --- a/src/java.base/share/classes/jdk/internal/io/JdkConsoleProvider.java +++ b/src/java.base/share/classes/jdk/internal/io/JdkConsoleProvider.java @@ -33,7 +33,7 @@ public interface JdkConsoleProvider { /** * The module name of the JdkConsole default provider. */ - String DEFAULT_PROVIDER_MODULE_NAME = "java.base"; + String DEFAULT_PROVIDER_MODULE_NAME = "jdk.internal.le"; /** * {@return the Console instance, or {@code null} if not available} 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 1be2d168769..2a2a24d26b4 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2023, 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 @@ -91,6 +91,7 @@ public class JdkConsoleProviderImpl implements JdkConsoleProvider { @Override public String readLine(String fmt, Object ... args) { try { + initJLineIfNeeded(); return jline.readLine(fmt.formatted(args)); } catch (EndOfFileException eofe) { return null; @@ -105,6 +106,7 @@ public class JdkConsoleProviderImpl implements JdkConsoleProvider { @Override public char[] readPassword(String fmt, Object ... args) { try { + initJLineIfNeeded(); return jline.readLine(fmt.formatted(args), '\0').toCharArray(); } catch (EndOfFileException eofe) { return null; @@ -126,12 +128,24 @@ public class JdkConsoleProviderImpl implements JdkConsoleProvider { return terminal.encoding(); } - private final LineReader jline; private final Terminal terminal; + private volatile LineReader jline; public JdkConsoleImpl(Terminal terminal) { this.terminal = terminal; - this.jline = LineReaderBuilder.builder().terminal(terminal).build(); + } + + private void initJLineIfNeeded() { + LineReader jline = this.jline; + if (jline == null) { + synchronized (this) { + jline = this.jline; + if (jline == null) { + jline = LineReaderBuilder.builder().terminal(terminal).build(); + this.jline = jline; + } + } + } } } } diff --git a/test/jdk/java/io/Console/ModuleSelectionTest.java b/test/jdk/java/io/Console/ModuleSelectionTest.java index 3ecf9a1a6fa..ab3a9babba3 100644 --- a/test/jdk/java/io/Console/ModuleSelectionTest.java +++ b/test/jdk/java/io/Console/ModuleSelectionTest.java @@ -27,7 +27,7 @@ * @summary Tests System.console() returns correct Console (or null) from the expected * module. * @modules java.base/java.io:+open - * @run main/othervm ModuleSelectionTest java.base + * @run main/othervm ModuleSelectionTest jdk.internal.le * @run main/othervm -Djdk.console=jdk.internal.le ModuleSelectionTest jdk.internal.le * @run main/othervm -Djdk.console=java.base ModuleSelectionTest java.base * @run main/othervm --limit-modules java.base ModuleSelectionTest java.base