
org.dellroad.jct.jshell.JShellShellSession Maven / Gradle / Ivy
/*
* Copyright (C) 2023 Archie L. Cobbs. All rights reserved.
*/
package org.dellroad.jct.jshell;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import jdk.jshell.tool.JavaShellToolBuilder;
import org.dellroad.javabox.execution.LocalContextExecutionControlProvider;
import org.dellroad.jct.core.AbstractShellSession;
import org.dellroad.jct.core.ShellRequest;
import org.dellroad.jct.core.ShellSession;
import org.dellroad.jct.core.util.ConsoleUtil;
import org.jline.terminal.Attributes;
import org.jline.terminal.Size;
import org.jline.terminal.Terminal;
/**
* A {@link ShellSession} that builds and executes a {@link jdk.jshell.JShell} instance.
*
*
* The associated {@link jdk.jshell.JShell} instance can be customized in two ways:
*
* - Override {@link #createBuilder createBuilder()} to customize the {@link JavaShellToolBuilder}
* used to create the JShell.
*
- Override {@link #modifyJShellParams modifyJShellParams()} to customize the flags and parameters
* passed to JShell itself (these are the same as accepted by the {@code jshell(1)} command line tool).
* By default, the parameters passed are the the parameters given on the command line.
*
*
*
* During execution, instances make themselves available to the current thread via {@link #getCurrent}.
*/
public class JShellShellSession extends AbstractShellSession {
private static final InheritableThreadLocal CURRENT_SESSION = new InheritableThreadLocal<>();
protected ClassLoader localContextClassLoader;
/**
* Constructor.
*
* @param shell owning shell
* @param request shell request
* @throws IllegalArgumentException if any parameter is null
*/
public JShellShellSession(JShellShell shell, ShellRequest request) {
super(shell, request);
}
// Public Methods
/**
* Get the instance associated with the current thread.
*
*
* This value is stored in an {@link InheritableThreadLocal} initialized when the JShell tool is started.
* As a result, it is accessible not only from the main JShell loop but also from the separate snippet
* execution threads created by JShell.
*
* @return session associated with the current thread, or null if not found
*/
public static JShellShellSession getCurrent() {
return CURRENT_SESSION.get();
}
/**
* Configure a class loader to use with {@link LocalContextExecutionControlProvider} for local execution.
*
* @param loader class loader, or null for none
* @see #modifyJShellParams
*/
public void setLocalContextClassLoader(ClassLoader loader) {
this.localContextClassLoader = loader;
}
// AbstractConsoleSession
@Override
public JShellShell getOwner() {
return (JShellShell)super.getOwner();
}
// AbstractShellSession
// JShell closes the output on exit, so we prevent that here
@Override
protected PrintStream buildOutputStream(Terminal terminal) {
return ConsoleUtil.unclosable(super.buildOutputStream(terminal));
}
@Override
protected int doExecute() throws InterruptedException {
final JavaShellToolBuilder builder = this.createBuilder();
final Terminal terminal = this.request.getTerminal();
final Attributes attr = terminal.enterRawMode();
final JShellShellSession previousSession = CURRENT_SESSION.get();
final List jshellParams = this.modifyJShellParams(this.request.getShellArguments());
if (jshellParams == null)
throw new IllegalArgumentException("null jshellParams");
CURRENT_SESSION.set(this);
final Thread currentThread = Thread.currentThread();
final ClassLoader previousLoader = currentThread.getContextClassLoader();
if (this.localContextClassLoader != null)
currentThread.setContextClassLoader(this.localContextClassLoader);
try {
final String[] params = jshellParams.toArray(new String[0]);
if (ConsoleUtil.getJavaVersion() >= 11) {
try {
// return builder.start(jshellParams);
return (int)JavaShellToolBuilder.class.getMethod("start", String[].class).invoke(builder, (Object)params);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("unexpected error: " + e.getCause(), e);
}
} else {
builder.run(params);
return 0;
}
} catch (Exception e) {
this.out.println(String.format("Error: %s", e));
return 1;
} finally {
CURRENT_SESSION.set(previousSession);
currentThread.setContextClassLoader(previousLoader);
terminal.setAttributes(attr);
}
}
// Subclass Methods
/**
* Create and configure the JShell builder for this new session.
*
* @return new builder
*/
protected JavaShellToolBuilder createBuilder() {
final JavaShellToolBuilder builder = JavaShellToolBuilder.builder();
builder.interactiveTerminal(true);
if (ConsoleUtil.getJavaVersion() >= 24) { // JDK-8332314
final Size windowSize = this.request.getTerminal().getSize();
if (windowSize != null) {
final int cols = windowSize.getColumns();
final int rows = windowSize.getRows();
try {
// builder.windowSize(cols, rows);
JavaShellToolBuilder.class.getMethod("windowSize", Integer.TYPE, Integer.TYPE).invoke(builder, cols, rows);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("unexpected error: " + e.getCause(), e);
}
}
}
builder.env(this.request.getEnvironment());
//builder.locale(???);
builder.in(this.in, this.in);
builder.out(this.out);
return builder;
}
/**
* Generate a list of command line flags and parameters to be passed to the JShell tool,
* given the arguments given on the shell command line for this command.
*
*
* If this method is overridden to add or change this command's flags and/or parameters,
* then the full constructor taking customized help detail should be used to describe the
* new usage.
*
*
* The implementation in {@link JShellShellSession} just returns the list unmodified unless
* a {@linkplain #setLocalContextClassLoader local context class loader} has been configured,
* in which case the list is copied, modified by {@link LocalContextExecutionControlProvider#modifyJShellFlags},
* and then returned.
*
* @param params parameters given to the shell command line
* @return flags and parameters for JShell
* @throws IllegalArgumentException if {@code commandLineParams} is null
* @see #setLocalContextClassLoader
*/
protected List modifyJShellParams(List params) {
if (params == null)
throw new IllegalArgumentException("null params");
if (this.localContextClassLoader != null) {
params = new ArrayList<>(params);
LocalContextExecutionControlProvider.modifyJShellFlags(this.localContextClassLoader, params);
}
return params;
}
}