Skip to content

Commit

Permalink
8296454: System.console() shouldn't return null in jshell
Browse files Browse the repository at this point in the history
Reviewed-by: vromero
  • Loading branch information
lahodaj committed Apr 5, 2023
1 parent 2aec910 commit 4bf1987
Show file tree
Hide file tree
Showing 15 changed files with 1,040 additions and 49 deletions.
Expand Up @@ -34,7 +34,9 @@
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Writer;
import java.net.URI;
import java.nio.charset.Charset;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -830,7 +832,8 @@ public void resume() {
@Override
public void beforeUserCode() {
synchronized (this) {
inputBytes = null;
pendingBytes = null;
pendingLine = null;
}
input.setState(State.BUFFER);
}
Expand Down Expand Up @@ -961,33 +964,73 @@ public void perform(LineReaderImpl in) throws IOException {
}
}

private byte[] inputBytes;
private int inputBytesPointer;
private String pendingLine;
private int pendingLinePointer;
private byte[] pendingBytes;
private int pendingBytesPointer;

@Override
public synchronized int readUserInput() throws IOException {
while (inputBytes == null || inputBytes.length <= inputBytesPointer) {
History prevHistory = in.getHistory();
boolean prevDisableCr = Display.DISABLE_CR;
Parser prevParser = in.getParser();
if (pendingBytes == null || pendingBytes.length <= pendingBytesPointer) {
char userChar = readUserInputChar();
pendingBytes = String.valueOf(userChar).getBytes();
pendingBytesPointer = 0;
}
return pendingBytes[pendingBytesPointer++];
}

try {
in.setParser((line, cursor, context) -> new ArgumentLine(line, cursor));
input.setState(State.WAIT);
Display.DISABLE_CR = true;
in.setHistory(userInputHistory);
inputBytes = (in.readLine("") + System.getProperty("line.separator")).getBytes();
inputBytesPointer = 0;
} catch (UserInterruptException ex) {
throw new InterruptedIOException();
} finally {
in.setParser(prevParser);
in.setHistory(prevHistory);
input.setState(State.BUFFER);
Display.DISABLE_CR = prevDisableCr;
}
@Override
public synchronized char readUserInputChar() throws IOException {
while (pendingLine == null || pendingLine.length() <= pendingLinePointer) {
pendingLine = doReadUserLine("", null) + System.getProperty("line.separator");
pendingLinePointer = 0;
}
return pendingLine.charAt(pendingLinePointer++);
}

@Override
public synchronized String readUserLine(String prompt) throws IOException {
//TODO: correct behavior w.r.t. pre-read stuff?
if (pendingLine != null && pendingLine.length() > pendingLinePointer) {
return pendingLine.substring(pendingLinePointer);
}
return doReadUserLine(prompt, null);
}

private synchronized String doReadUserLine(String prompt, Character mask) throws IOException {
History prevHistory = in.getHistory();
boolean prevDisableCr = Display.DISABLE_CR;
Parser prevParser = in.getParser();

try {
in.setParser((line, cursor, context) -> new ArgumentLine(line, cursor));
input.setState(State.WAIT);
Display.DISABLE_CR = true;
in.setHistory(userInputHistory);
return in.readLine(prompt, mask);
} catch (UserInterruptException ex) {
throw new InterruptedIOException();
} finally {
in.setParser(prevParser);
in.setHistory(prevHistory);
input.setState(State.BUFFER);
Display.DISABLE_CR = prevDisableCr;
}
return inputBytes[inputBytesPointer++];
}

public char[] readPassword(String prompt) throws IOException {
//TODO: correct behavior w.r.t. pre-read stuff?
return doReadUserLine(prompt, '\0').toCharArray();
}

@Override
public Charset charset() {
return in.getTerminal().encoding();
}

@Override
public Writer userOutput() {
return in.getTerminal().writer();
}

private int countPendingOpenBraces(String code) {
Expand Down
Expand Up @@ -26,6 +26,9 @@
package jdk.internal.jshell.tool;

import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import jdk.internal.org.jline.reader.UserInterruptException;

/**
* Interface for defining user interaction with the shell.
Expand Down Expand Up @@ -56,8 +59,28 @@ abstract class IOContext implements AutoCloseable {

public abstract int readUserInput() throws IOException;

public char readUserInputChar() throws IOException {
throw new UserInterruptException("");
}

public String readUserLine(String prompt) throws IOException {
throw new UserInterruptException("");
}

public Writer userOutput() {
throw new UnsupportedOperationException();
}

public char[] readPassword(String prompt) throws IOException {
throw new UserInterruptException("");
}

public void setIndent(int indent) {}

public Charset charset() {
throw new UnsupportedOperationException();
}

class InputInterruptedException extends Exception {
private static final long serialVersionUID = 1L;
}
Expand Down
Expand Up @@ -31,12 +31,15 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
Expand Down Expand Up @@ -117,6 +120,7 @@
import jdk.internal.jshell.tool.Selector.FormatWhen;
import jdk.internal.editor.spi.BuildInEditorProvider;
import jdk.internal.editor.external.ExternalEditor;
import jdk.internal.org.jline.reader.UserInterruptException;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.singletonList;
Expand All @@ -131,6 +135,7 @@
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_WRAP;
import static jdk.internal.jshell.tool.ContinuousCompletionProvider.STARTSWITH_MATCHER;
import jdk.jshell.JShellConsole;

/**
* Command line REPL tool for Java using the JShell API.
Expand Down Expand Up @@ -1093,6 +1098,7 @@ private void resetState() {
.in(userin)
.out(userout)
.err(usererr)
.console(new IOContextConsole())
.tempVariableNameGenerator(() -> "$" + currentNameSpace.tidNext())
.idGenerator((sn, i) -> (currentNameSpace == startNamespace || state.status(sn).isActive())
? currentNameSpace.tid(sn)
Expand Down Expand Up @@ -4031,6 +4037,84 @@ public boolean matchesType() {
return false;
}
}

private final class IOContextConsole implements JShellConsole {

private Reader reader;
private PrintWriter writer;

@Override
public synchronized PrintWriter writer() {
if (writer == null) {
writer = new PrintWriter(new Writer() {
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
input.userOutput().write(cbuf, off, len);
}
@Override
public void flush() throws IOException {
input.userOutput().flush();
}
@Override
public void close() throws IOException {
input.userOutput().close();
}
});
}
return writer;
}

@Override
public synchronized Reader reader() {
if (reader == null) {
reader = new Reader() {
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
if (len == 0) return 0;
try {
cbuf[off] = input.readUserInputChar();
return 1;
} catch (UserInterruptException ex) {
return -1;
}
}

@Override
public void close() throws IOException {
}
};
}
return reader;
}

@Override
public String readLine(String prompt) {
try {
return input.readUserLine(prompt);
} catch (IOException ex) {
throw new IOError(ex);
}
}

@Override
public char[] readPassword(String prompt) {
try {
return input.readPassword(prompt);
} catch (IOException ex) {
throw new IOError(ex);
}
}

@Override
public void flush() {
writer().flush();
}

@Override
public Charset charset() {
return input.charset();
}
}
}

abstract class NonInteractiveIOContext extends IOContext {
Expand Down
25 changes: 25 additions & 0 deletions src/jdk.jshell/share/classes/jdk/jshell/JShell.java
Expand Up @@ -40,6 +40,7 @@
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.BiFunction;
import java.util.function.Consumer;
Expand Down Expand Up @@ -92,6 +93,7 @@ public class JShell implements AutoCloseable {
final InputStream in;
final PrintStream out;
final PrintStream err;
final Optional<JShellConsole> console;
final Supplier<String> tempVariableNameGenerator;
final BiFunction<Snippet, Integer, String> idGenerator;
final List<String> extraRemoteVMOptions;
Expand All @@ -116,6 +118,7 @@ public class JShell implements AutoCloseable {
this.in = b.in;
this.out = b.out;
this.err = b.err;
this.console = Optional.ofNullable(b.console);
this.tempVariableNameGenerator = b.tempVariableNameGenerator;
this.idGenerator = b.idGenerator;
this.extraRemoteVMOptions = b.extraRemoteVMOptions;
Expand Down Expand Up @@ -170,6 +173,7 @@ public static class Builder {
InputStream in = new ByteArrayInputStream(new byte[0]);
PrintStream out = System.out;
PrintStream err = System.err;
JShellConsole console = null;
Supplier<String> tempVariableNameGenerator = null;
BiFunction<Snippet, Integer, String> idGenerator = null;
List<String> extraRemoteVMOptions = new ArrayList<>();
Expand Down Expand Up @@ -238,6 +242,22 @@ public Builder err(PrintStream err) {
return this;
}

/**
* Sets the console for the running evalution.
* <p>
* The default, if this is not set, is no console ({@code System.console()}
* will return {@code null} while running a snippet).
*
* @param console console to use while a snippet is run
* @return the {@code Builder} instance (for use in chained
* initialization)
* @since 21
*/
public Builder console(JShellConsole console) {
this.console = console;
return this;
}

/**
* Sets a generator of temp variable names for
* {@link jdk.jshell.VarSnippet} of
Expand Down Expand Up @@ -795,6 +815,11 @@ public void closeDown() {
JShell.this.closeDown();
}

@Override
public Optional<JShellConsole> console() {
return console;
}

}

// --- private / package-private implementation support ---
Expand Down

1 comment on commit 4bf1987

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.