/*
 * Copyright (c) 2015, 2017, 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * 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 jdk.jshell;

import jdk.jshell.spi.ExecutionControl;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.PrintStream;
import java.net.InetAddress;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.function.BiFunction;
import java.util.function.Consumer;

import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.tools.StandardJavaFileManager;
import jdk.internal.jshell.debug.InternalDebugControl;
import jdk.jshell.Snippet.Status;
import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
import jdk.jshell.spi.ExecutionControlProvider;
import jdk.jshell.spi.ExecutionEnv;
import static jdk.jshell.Util.expunge;

The JShell evaluation state engine. This is the central class in the JShell API. A JShell instance holds the evolving compilation and execution state. The state is changed with the instance methods eval(String), drop(Snippet) and addToClasspath(String). The majority of methods query the state. A JShell instance also allows registering for events with onSnippetEvent(Consumer) and onShutdown(Consumer), which are unregistered with unsubscribe(Subscription). Access to the source analysis utilities is via sourceCodeAnalysis(). When complete the instance should be closed to free resources -- close().

An instance of JShell is created with JShell.create().

This class is not thread safe, except as noted, all access should be through a single thread.

Author:Robert Field
Since:9
/** * The JShell evaluation state engine. This is the central class in the JShell * API. A {@code JShell} instance holds the evolving compilation and * execution state. The state is changed with the instance methods * {@link jdk.jshell.JShell#eval(java.lang.String) eval(String)}, * {@link jdk.jshell.JShell#drop(jdk.jshell.Snippet) drop(Snippet)} and * {@link jdk.jshell.JShell#addToClasspath(java.lang.String) addToClasspath(String)}. * The majority of methods query the state. * A {@code JShell} instance also allows registering for events with * {@link jdk.jshell.JShell#onSnippetEvent(java.util.function.Consumer) onSnippetEvent(Consumer)} * and {@link jdk.jshell.JShell#onShutdown(java.util.function.Consumer) onShutdown(Consumer)}, which * are unregistered with * {@link jdk.jshell.JShell#unsubscribe(jdk.jshell.JShell.Subscription) unsubscribe(Subscription)}. * Access to the source analysis utilities is via * {@link jdk.jshell.JShell#sourceCodeAnalysis()}. * When complete the instance should be closed to free resources -- * {@link jdk.jshell.JShell#close()}. * <p> * An instance of {@code JShell} is created with * {@code JShell.create()}. * <p> * This class is not thread safe, except as noted, all access should be through * a single thread. * * @author Robert Field * @since 9 */
public class JShell implements AutoCloseable { final SnippetMaps maps; final KeyMap keyMap; final OuterWrapMap outerMap; final TaskFactory taskFactory; final InputStream in; final PrintStream out; final PrintStream err; final Supplier<String> tempVariableNameGenerator; final BiFunction<Snippet, Integer, String> idGenerator; final List<String> extraRemoteVMOptions; final List<String> extraCompilerOptions; final Function<StandardJavaFileManager, StandardJavaFileManager> fileManagerMapping; private int nextKeyIndex = 1; final Eval eval; final ClassTracker classTracker; private final Map<Subscription, Consumer<JShell>> shutdownListeners = new HashMap<>(); private final Map<Subscription, Consumer<SnippetEvent>> keyStatusListeners = new HashMap<>(); private boolean closed = false; private final ExecutionControl executionControl; private SourceCodeAnalysisImpl sourceCodeAnalysis = null; private static final String L10N_RB_NAME = "jdk.jshell.resources.l10n"; private static ResourceBundle outputRB = null; JShell(Builder b) throws IllegalStateException { this.in = b.in; this.out = b.out; this.err = b.err; this.tempVariableNameGenerator = b.tempVariableNameGenerator; this.idGenerator = b.idGenerator; this.extraRemoteVMOptions = b.extraRemoteVMOptions; this.extraCompilerOptions = b.extraCompilerOptions; this.fileManagerMapping = b.fileManagerMapping; try { if (b.executionControlProvider != null) { executionControl = b.executionControlProvider.generate(new ExecutionEnvImpl(), b.executionControlParameters == null ? b.executionControlProvider.defaultParameters() : b.executionControlParameters); } else { String loopback = InetAddress.getLoopbackAddress().getHostAddress(); String spec = b.executionControlSpec == null ? "failover:0(jdi:hostname(" + loopback + "))," + "1(jdi:launch(true)), 2(jdi)" : b.executionControlSpec; executionControl = ExecutionControl.generate(new ExecutionEnvImpl(), spec); } } catch (Throwable ex) { throw new IllegalStateException("Launching JShell execution engine threw: " + ex.getMessage(), ex); } this.maps = new SnippetMaps(this); this.keyMap = new KeyMap(this); this.outerMap = new OuterWrapMap(this); this.taskFactory = new TaskFactory(this); this.eval = new Eval(this); this.classTracker = new ClassTracker(); }
Builder for JShell instances. Create custom instances of JShell by using the setter methods on this class. After zero or more of these, use the build() method to create a JShell instance. These can all be chained. For example, setting the remote output and error streams:

    JShell myShell =
      JShell.builder()
        .out(myOutStream)
        .err(myErrStream)
        .build();  
If no special set-up is needed, just use JShell.builder().build() or the short-cut equivalent JShell.create().
/** * Builder for {@code JShell} instances. * Create custom instances of {@code JShell} by using the setter * methods on this class. After zero or more of these, use the * {@link #build()} method to create a {@code JShell} instance. * These can all be chained. For example, setting the remote output and * error streams: * <pre> * {@code * JShell myShell = * JShell.builder() * .out(myOutStream) * .err(myErrStream) * .build(); } </pre> * If no special set-up is needed, just use * {@code JShell.builder().build()} or the short-cut equivalent * {@code JShell.create()}. */
public static class Builder { InputStream in = new ByteArrayInputStream(new byte[0]); PrintStream out = System.out; PrintStream err = System.err; Supplier<String> tempVariableNameGenerator = null; BiFunction<Snippet, Integer, String> idGenerator = null; List<String> extraRemoteVMOptions = new ArrayList<>(); List<String> extraCompilerOptions = new ArrayList<>(); ExecutionControlProvider executionControlProvider; Map<String,String> executionControlParameters; String executionControlSpec; Function<StandardJavaFileManager, StandardJavaFileManager> fileManagerMapping; Builder() { }
Sets the input for the running evaluation (it's System.in). Note: applications that use System.in for snippet or other user input cannot use System.in as the input stream for the remote process.

The read method of the InputStream may throw the InterruptedIOException to signal the user canceled the input. The currently running snippet will be automatically stopped.

The default, if this is not set, is to provide an empty input stream -- new ByteArrayInputStream(new byte[0]).

Params:
  • in – the InputStream to be channelled to System.in in the remote execution process
Returns:the Builder instance (for use in chained initialization)
/** * Sets the input for the running evaluation (it's {@code System.in}). Note: * applications that use {@code System.in} for snippet or other * user input cannot use {@code System.in} as the input stream for * the remote process. * <p> * The {@code read} method of the {@code InputStream} may throw the {@link InterruptedIOException} * to signal the user canceled the input. The currently running snippet will be automatically * {@link JShell#stop() stopped}. * <p> * The default, if this is not set, is to provide an empty input stream * -- {@code new ByteArrayInputStream(new byte[0])}. * * @param in the {@code InputStream} to be channelled to * {@code System.in} in the remote execution process * @return the {@code Builder} instance (for use in chained * initialization) */
public Builder in(InputStream in) { this.in = in; return this; }
Sets the output for the running evaluation (it's System.out). The controlling process and the remote process can share System.out.

The default, if this is not set, is System.out.

Params:
  • out – the PrintStream to be channelled to System.out in the remote execution process
Returns:the Builder instance (for use in chained initialization)
/** * Sets the output for the running evaluation (it's {@code System.out}). * The controlling process and * the remote process can share {@code System.out}. * <p> * The default, if this is not set, is {@code System.out}. * * @param out the {@code PrintStream} to be channelled to * {@code System.out} in the remote execution process * @return the {@code Builder} instance (for use in chained * initialization) */
public Builder out(PrintStream out) { this.out = out; return this; }
Sets the error output for the running evaluation (it's System.err). The controlling process and the remote process can share System.err.

The default, if this is not set, is System.err.

Params:
  • err – the PrintStream to be channelled to System.err in the remote execution process
Returns:the Builder instance (for use in chained initialization)
/** * Sets the error output for the running evaluation (it's * {@code System.err}). The controlling process and the remote * process can share {@code System.err}. * <p> * The default, if this is not set, is {@code System.err}. * * @param err the {@code PrintStream} to be channelled to * {@code System.err} in the remote execution process * @return the {@code Builder} instance (for use in chained * initialization) */
public Builder err(PrintStream err) { this.err = err; return this; }
Sets a generator of temp variable names for VarSnippet of SubKind.TEMP_VAR_EXPRESSION_SUBKIND.

Do not use this method unless you have explicit need for it.

The generator will be used for newly created VarSnippet instances. The name of a variable is queried with PersistentSnippet.name().

The callback is sent during the processing of the snippet, the JShell state is not stable. No calls whatsoever on the JShell instance may be made from the callback.

The generated name must be unique within active snippets.

The default behavior (if this is not set or generator is null) is to generate the name as a sequential number with a prefixing dollar sign ("$").

Params:
  • generator – the Supplier to generate the temporary variable name string or null
Returns:the Builder instance (for use in chained initialization)
/** * Sets a generator of temp variable names for * {@link jdk.jshell.VarSnippet} of * {@link jdk.jshell.Snippet.SubKind#TEMP_VAR_EXPRESSION_SUBKIND}. * <p> * Do not use this method unless you have explicit need for it. * <p> * The generator will be used for newly created VarSnippet * instances. The name of a variable is queried with * {@link jdk.jshell.VarSnippet#name()}. * <p> * The callback is sent during the processing of the snippet, the * JShell state is not stable. No calls whatsoever on the * {@code JShell} instance may be made from the callback. * <p> * The generated name must be unique within active snippets. * <p> * The default behavior (if this is not set or {@code generator} * is null) is to generate the name as a sequential number with a * prefixing dollar sign ("$"). * * @param generator the {@code Supplier} to generate the temporary * variable name string or {@code null} * @return the {@code Builder} instance (for use in chained * initialization) */
public Builder tempVariableNameGenerator(Supplier<String> generator) { this.tempVariableNameGenerator = generator; return this; }
Sets the generator of identifying names for Snippets.

Do not use this method unless you have explicit need for it.

The generator will be used for newly created Snippet instances. The identifying name (id) is accessed with Snippet.id() and can be seen in the StackTraceElement.getFileName() for a EvalException and UnresolvedReferenceException.

The inputs to the generator are the Snippet and an integer. The integer will be the same for two Snippets which would overwrite one-another, but otherwise is unique.

The callback is sent during the processing of the snippet and the Snippet and the state as a whole are not stable. No calls to change system state (including Snippet state) should be made. Queries of Snippet may be made except to Snippet.id(). No calls on the JShell instance may be made from the callback, except to status(Snippet).

The default behavior (if this is not set or generator is null) is to generate the id as the integer converted to a string.

Params:
  • generator – the BiFunction to generate the id string or null
Returns:the Builder instance (for use in chained initialization)
/** * Sets the generator of identifying names for Snippets. * <p> * Do not use this method unless you have explicit need for it. * <p> * The generator will be used for newly created Snippet instances. The * identifying name (id) is accessed with * {@link jdk.jshell.Snippet#id()} and can be seen in the * {@code StackTraceElement.getFileName()} for a * {@link jdk.jshell.EvalException} and * {@link jdk.jshell.UnresolvedReferenceException}. * <p> * The inputs to the generator are the {@link jdk.jshell.Snippet} and an * integer. The integer will be the same for two Snippets which would * overwrite one-another, but otherwise is unique. * <p> * The callback is sent during the processing of the snippet and the * Snippet and the state as a whole are not stable. No calls to change * system state (including Snippet state) should be made. Queries of * Snippet may be made except to {@link jdk.jshell.Snippet#id()}. No * calls on the {@code JShell} instance may be made from the * callback, except to * {@link #status(jdk.jshell.Snippet) status(Snippet)}. * <p> * The default behavior (if this is not set or {@code generator} * is null) is to generate the id as the integer converted to a string. * * @param generator the {@code BiFunction} to generate the id * string or {@code null} * @return the {@code Builder} instance (for use in chained * initialization) */
public Builder idGenerator(BiFunction<Snippet, Integer, String> generator) { this.idGenerator = generator; return this; }
Sets additional VM options for launching the VM.
Params:
  • options – The options for the remote VM
Returns:the Builder instance (for use in chained initialization)
/** * Sets additional VM options for launching the VM. * * @param options The options for the remote VM * @return the {@code Builder} instance (for use in chained * initialization) */
public Builder remoteVMOptions(String... options) { this.extraRemoteVMOptions.addAll(Arrays.asList(options)); return this; }
Adds compiler options. These additional options will be used on parsing, analysis, and code generation calls to the compiler. Options which interfere with results are not supported and have undefined effects on JShell's operation.
Params:
  • options – the addition options for compiler invocations
Returns:the Builder instance (for use in chained initialization)
/** * Adds compiler options. These additional options will be used on * parsing, analysis, and code generation calls to the compiler. * Options which interfere with results are not supported and have * undefined effects on JShell's operation. * * @param options the addition options for compiler invocations * @return the {@code Builder} instance (for use in chained * initialization) */
public Builder compilerOptions(String... options) { this.extraCompilerOptions.addAll(Arrays.asList(options)); return this; }
Sets the custom engine for execution. Snippet execution will be provided by the ExecutionControl instance selected by the specified execution control spec. Use, at most, one of these overloaded executionEngine builder methods.
Params:
  • executionControlSpec – the execution control spec, which is documented in the spi package documentation.
Returns:the Builder instance (for use in chained initialization)
/** * Sets the custom engine for execution. Snippet execution will be * provided by the {@link ExecutionControl} instance selected by the * specified execution control spec. * Use, at most, one of these overloaded {@code executionEngine} builder * methods. * * @param executionControlSpec the execution control spec, * which is documented in the {@link jdk.jshell.spi} * package documentation. * @return the {@code Builder} instance (for use in chained * initialization) */
public Builder executionEngine(String executionControlSpec) { this.executionControlSpec = executionControlSpec; return this; }
Sets the custom engine for execution. Snippet execution will be provided by the specified ExecutionControl instance. Use, at most, one of these overloaded executionEngine builder methods.
Params:
  • executionControlProvider – the provider to supply the execution engine
  • executionControlParameters – the parameters to the provider, or null for default parameters
Returns:the Builder instance (for use in chained initialization)
/** * Sets the custom engine for execution. Snippet execution will be * provided by the specified {@link ExecutionControl} instance. * Use, at most, one of these overloaded {@code executionEngine} builder * methods. * * @param executionControlProvider the provider to supply the execution * engine * @param executionControlParameters the parameters to the provider, or * {@code null} for default parameters * @return the {@code Builder} instance (for use in chained * initialization) */
public Builder executionEngine(ExecutionControlProvider executionControlProvider, Map<String,String> executionControlParameters) { this.executionControlProvider = executionControlProvider; this.executionControlParameters = executionControlParameters; return this; }
Configure the FileManager to be used by compilation and source analysis. If not set or passed null, the compiler's standard file manager will be used (identity mapping). For use in special applications where the compiler's normal file handling needs to be overridden. See the file manager APIs for more information. The file manager input enables forwarding file managers, if this is not needed, the incoming file manager can be ignored (constant function).
Params:
  • mapping – a function that given the compiler's standard file manager, returns a file manager to use
Returns:the Builder instance (for use in chained initialization)
/** * Configure the {@code FileManager} to be used by compilation and * source analysis. * If not set or passed null, the compiler's standard file manager will * be used (identity mapping). * For use in special applications where the compiler's normal file * handling needs to be overridden. See the file manager APIs for more * information. * The file manager input enables forwarding file managers, if this * is not needed, the incoming file manager can be ignored (constant * function). * * @param mapping a function that given the compiler's standard file * manager, returns a file manager to use * @return the {@code Builder} instance (for use in chained * initialization) */
public Builder fileManager(Function<StandardJavaFileManager, StandardJavaFileManager> mapping) { this.fileManagerMapping = mapping; return this; }
Builds a JShell state engine. This is the entry-point to all JShell functionality. This creates a remote process for execution. It is thus important to close the returned instance.
Throws:
  • IllegalStateException – if the JShell instance could not be created.
Returns:the state engine
/** * Builds a JShell state engine. This is the entry-point to all JShell * functionality. This creates a remote process for execution. It is * thus important to close the returned instance. * * @throws IllegalStateException if the {@code JShell} instance could not be created. * @return the state engine */
public JShell build() throws IllegalStateException { return new JShell(this); } } // --- public API ---
Create a new JShell state engine. That is, create an instance of JShell.

Equivalent to JShell.builder().build().

Throws:
Returns:an instance of JShell.
/** * Create a new JShell state engine. * That is, create an instance of {@code JShell}. * <p> * Equivalent to {@link JShell#builder() JShell.builder()}{@link JShell.Builder#build() .build()}. * @throws IllegalStateException if the {@code JShell} instance could not be created. * @return an instance of {@code JShell}. */
public static JShell create() throws IllegalStateException { return builder().build(); }
Factory method for JShell.Builder which, in-turn, is used for creating instances of JShell. Create a default instance of JShell with JShell.builder().build(). For more construction options see Builder.
See Also:
Returns:an instance of Builder.
/** * Factory method for {@code JShell.Builder} which, in-turn, is used * for creating instances of {@code JShell}. * Create a default instance of {@code JShell} with * {@code JShell.builder().build()}. For more construction options * see {@link jdk.jshell.JShell.Builder}. * @return an instance of {@code Builder}. * @see jdk.jshell.JShell.Builder */
public static Builder builder() { return new Builder(); }
Access to source code analysis functionality. An instance of JShell will always return the same SourceCodeAnalysis instance from sourceCodeAnalysis().
Returns:an instance of SourceCodeAnalysis which can be used for source analysis such as completion detection and completion suggestions.
/** * Access to source code analysis functionality. * An instance of {@code JShell} will always return the same * {@code SourceCodeAnalysis} instance from * {@code sourceCodeAnalysis()}. * @return an instance of {@link SourceCodeAnalysis SourceCodeAnalysis} * which can be used for source analysis such as completion detection and * completion suggestions. */
public SourceCodeAnalysis sourceCodeAnalysis() { if (sourceCodeAnalysis == null) { sourceCodeAnalysis = new SourceCodeAnalysisImpl(this); } return sourceCodeAnalysis; }
Evaluate the input String, including definition and/or execution, if applicable. The input is checked for errors, unless the errors can be deferred (as is the case with some unresolvedDependencies references), errors will abort evaluation.

The input should be exactly one complete snippet of source code, that is, one expression, statement, variable declaration, method declaration, class declaration, or import. To break arbitrary input into individual complete snippets, use SourceCodeAnalysis.analyzeCompletion(String).

For imports, the import is added. Classes, interfaces. methods, and variables are defined. The initializer of variables, statements, and expressions are executed. The modifiers public, protected, private, static, and final are not allowed on op-level declarations and are ignored with a warning. Synchronized, native, abstract, and default top-level methods are not allowed and are errors. If a previous definition of a declaration is overwritten then there will be an event showing its status changed to OVERWRITTEN, this will not occur for dropped, rejected, or already overwritten declarations.

If execution environment is out of process, as is the default case, then if the evaluated code causes the execution environment to terminate, this JShell instance will be closed but the calling process and VM remain valid.

Params:
  • input – The input String to evaluate
Throws:
See Also:
Returns:the list of events directly or indirectly caused by this evaluation.
/** * Evaluate the input String, including definition and/or execution, if * applicable. The input is checked for errors, unless the errors can be * deferred (as is the case with some unresolvedDependencies references), * errors will abort evaluation. * <p> * The input should be * exactly one complete snippet of source code, that is, one expression, * statement, variable declaration, method declaration, class declaration, * or import. * To break arbitrary input into individual complete snippets, use * {@link SourceCodeAnalysis#analyzeCompletion(String)}. * <p> * For imports, the import is added. Classes, interfaces. methods, * and variables are defined. The initializer of variables, statements, * and expressions are executed. * The modifiers public, protected, private, static, and final are not * allowed on op-level declarations and are ignored with a warning. * Synchronized, native, abstract, and default top-level methods are not * allowed and are errors. * If a previous definition of a declaration is overwritten then there will * be an event showing its status changed to OVERWRITTEN, this will not * occur for dropped, rejected, or already overwritten declarations. * <p> * If execution environment is out of process, as is the default case, then * if the evaluated code * causes the execution environment to terminate, this {@code JShell} * instance will be closed but the calling process and VM remain valid. * @param input The input String to evaluate * @return the list of events directly or indirectly caused by this evaluation. * @throws IllegalStateException if this {@code JShell} instance is closed. * @see SourceCodeAnalysis#analyzeCompletion(String) * @see JShell#onShutdown(java.util.function.Consumer) */
public List<SnippetEvent> eval(String input) throws IllegalStateException { SourceCodeAnalysisImpl a = sourceCodeAnalysis; if (a != null) { a.suspendIndexing(); } try { checkIfAlive(); List<SnippetEvent> events = eval.eval(input); events.forEach(this::notifyKeyStatusEvent); return Collections.unmodifiableList(events); } finally { if (a != null) { a.resumeIndexing(); } } }
Remove a declaration from the state. That is, if the snippet is an active persistent snippet, remove the snippet and update the JShell evaluation state accordingly. For all active snippets, change the status to DROPPED.
Params:
  • snippet – The snippet to remove
Throws:
Returns:The list of events from updating declarations dependent on the dropped snippet.
/** * Remove a declaration from the state. That is, if the snippet is an * {@linkplain jdk.jshell.Snippet.Status#isActive() active} * {@linkplain jdk.jshell.PersistentSnippet persistent} snippet, remove the * snippet and update the JShell evaluation state accordingly. * For all active snippets, change the {@linkplain #status status} to * {@link jdk.jshell.Snippet.Status#DROPPED DROPPED}. * @param snippet The snippet to remove * @return The list of events from updating declarations dependent on the * dropped snippet. * @throws IllegalStateException if this {@code JShell} instance is closed. * @throws IllegalArgumentException if the snippet is not associated with * this {@code JShell} instance. */
public List<SnippetEvent> drop(Snippet snippet) throws IllegalStateException { checkIfAlive(); checkValidSnippet(snippet); List<SnippetEvent> events = eval.drop(snippet); events.forEach(this::notifyKeyStatusEvent); return Collections.unmodifiableList(events); }
The specified path is added to the end of the classpath used in eval(). Note that the unnamed package is not accessible from the package in which eval(String) code is placed.
Params:
  • path – the path to add to the classpath.
Throws:
/** * The specified path is added to the end of the classpath used in eval(). * Note that the unnamed package is not accessible from the package in which * {@link JShell#eval(String)} code is placed. * @param path the path to add to the classpath. * @throws IllegalStateException if this {@code JShell} instance is closed. */
public void addToClasspath(String path) { checkIfAlive(); // Compiler taskFactory.addToClasspath(path); // Runtime try { executionControl().addToClasspath(path); } catch (ExecutionControlException ex) { debug(ex, "on addToClasspath(" + path + ")"); } if (sourceCodeAnalysis != null) { sourceCodeAnalysis.classpathChanged(); } }
Attempt to stop currently running evaluation. When called while the eval(String) method is running and the user's code being executed, an attempt will be made to stop user's code. Note that typically this method needs to be called from a different thread than the one running the eval method.

If the eval(String) method is not running, does nothing.

The attempt to stop the user's code may fail in some case, which may include when the execution is blocked on an I/O operation, or when the user's code is catching the ThreadDeath exception.

/** * Attempt to stop currently running evaluation. When called while * the {@link #eval(java.lang.String) } method is running and the * user's code being executed, an attempt will be made to stop user's code. * Note that typically this method needs to be called from a different thread * than the one running the {@code eval} method. * <p> * If the {@link #eval(java.lang.String) } method is not running, does nothing. * <p> * The attempt to stop the user's code may fail in some case, which may include * when the execution is blocked on an I/O operation, or when the user's code is * catching the {@link ThreadDeath} exception. */
public void stop() { if (executionControl != null) { try { executionControl.stop(); } catch (ExecutionControlException ex) { debug(ex, "on stop()"); } } }
Close this state engine. Frees resources. Should be called when this state engine is no longer needed.
/** * Close this state engine. Frees resources. Should be called when this * state engine is no longer needed. */
@Override public void close() { closeDown(); }
Return all snippets.
Returns:the snippets for all current snippets in id order.
/** * Return all snippets. * @return the snippets for all current snippets in id order. */
public Stream<Snippet> snippets() { return maps.snippetList().stream(); }
Returns the active variable snippets. This convenience method is equivalent to snippets() filtered for status(snippet).isActive() && snippet.kind() == Kind.VARIABLE and cast to VarSnippet.
Returns:the active declared variables.
/** * Returns the active variable snippets. * This convenience method is equivalent to {@code snippets()} filtered for * {@link jdk.jshell.Snippet.Status#isActive() status(snippet).isActive()} * {@code && snippet.kind() == Kind.VARIABLE} * and cast to {@code VarSnippet}. * @return the active declared variables. */
public Stream<VarSnippet> variables() { return snippets() .filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.VAR) .map(sn -> (VarSnippet) sn); }
Returns the active method snippets. This convenience method is equivalent to snippets() filtered for status(snippet).isActive() && snippet.kind() == Kind.METHOD and cast to MethodSnippet.
Returns:the active declared methods.
/** * Returns the active method snippets. * This convenience method is equivalent to {@code snippets()} filtered for * {@link jdk.jshell.Snippet.Status#isActive() status(snippet).isActive()} * {@code && snippet.kind() == Kind.METHOD} * and cast to MethodSnippet. * @return the active declared methods. */
public Stream<MethodSnippet> methods() { return snippets() .filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.METHOD) .map(sn -> (MethodSnippet)sn); }
Returns the active type declaration (class, interface, annotation type, and enum) snippets. This convenience method is equivalent to snippets() filtered for status(snippet).isActive() && snippet.kind() == Kind.TYPE_DECL and cast to TypeDeclSnippet.
Returns:the active declared type declarations.
/** * Returns the active type declaration (class, interface, annotation type, and enum) snippets. * This convenience method is equivalent to {@code snippets()} filtered for * {@link jdk.jshell.Snippet.Status#isActive() status(snippet).isActive()} * {@code && snippet.kind() == Kind.TYPE_DECL} * and cast to TypeDeclSnippet. * @return the active declared type declarations. */
public Stream<TypeDeclSnippet> types() { return snippets() .filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.TYPE_DECL) .map(sn -> (TypeDeclSnippet) sn); }
Returns the active import snippets. This convenience method is equivalent to snippets() filtered for status(snippet).isActive() && snippet.kind() == Kind.IMPORT and cast to ImportSnippet.
Returns:the active declared import declarations.
/** * Returns the active import snippets. * This convenience method is equivalent to {@code snippets()} filtered for * {@link jdk.jshell.Snippet.Status#isActive() status(snippet).isActive()} * {@code && snippet.kind() == Kind.IMPORT} * and cast to ImportSnippet. * @return the active declared import declarations. */
public Stream<ImportSnippet> imports() { return snippets() .filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.IMPORT) .map(sn -> (ImportSnippet) sn); }
Return the status of the snippet. This is updated either because of an explicit eval() call or an automatic update triggered by a dependency.
Params:
  • snippet – the Snippet to look up
Throws:
Returns:the status corresponding to this snippet
/** * Return the status of the snippet. * This is updated either because of an explicit {@code eval()} call or * an automatic update triggered by a dependency. * @param snippet the {@code Snippet} to look up * @return the status corresponding to this snippet * @throws IllegalStateException if this {@code JShell} instance is closed. * @throws IllegalArgumentException if the snippet is not associated with * this {@code JShell} instance. */
public Status status(Snippet snippet) { return checkValidSnippet(snippet).status(); }
Return the diagnostics of the most recent evaluation of the snippet. The evaluation can either because of an explicit eval() call or an automatic update triggered by a dependency.
Params:
  • snippet – the Snippet to look up
Throws:
Returns:the diagnostics corresponding to this snippet. This does not include unresolvedDependencies references reported in unresolvedDependencies().
/** * Return the diagnostics of the most recent evaluation of the snippet. * The evaluation can either because of an explicit {@code eval()} call or * an automatic update triggered by a dependency. * @param snippet the {@code Snippet} to look up * @return the diagnostics corresponding to this snippet. This does not * include unresolvedDependencies references reported in {@code unresolvedDependencies()}. * @throws IllegalStateException if this {@code JShell} instance is closed. * @throws IllegalArgumentException if the snippet is not associated with * this {@code JShell} instance. */
public Stream<Diag> diagnostics(Snippet snippet) { return checkValidSnippet(snippet).diagnostics().stream(); }
For RECOVERABLE_DEFINED or RECOVERABLE_NOT_DEFINED declarations, the names of current unresolved dependencies for the snippet. The returned value of this method, for a given method may change when an eval() or drop() of another snippet causes an update of a dependency.
Params:
  • snippet – the declaration Snippet to look up
Throws:
Returns:a stream of symbol names that are currently unresolvedDependencies.
/** * For {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED} or * {@link jdk.jshell.Snippet.Status#RECOVERABLE_NOT_DEFINED RECOVERABLE_NOT_DEFINED} * declarations, the names of current unresolved dependencies for * the snippet. * The returned value of this method, for a given method may change when an * {@code eval()} or {@code drop()} of another snippet causes * an update of a dependency. * @param snippet the declaration {@code Snippet} to look up * @return a stream of symbol names that are currently unresolvedDependencies. * @throws IllegalStateException if this {@code JShell} instance is closed. * @throws IllegalArgumentException if the snippet is not associated with * this {@code JShell} instance. */
public Stream<String> unresolvedDependencies(DeclarationSnippet snippet) { return checkValidSnippet(snippet).unresolved().stream(); }
Get the current value of a variable.
Params:
  • snippet – the variable Snippet whose value is queried.
Throws:
Returns:the current value of the variable referenced by snippet.
/** * Get the current value of a variable. * @param snippet the variable Snippet whose value is queried. * @return the current value of the variable referenced by snippet. * @throws IllegalStateException if this {@code JShell} instance is closed. * @throws IllegalArgumentException if the snippet is not associated with * this {@code JShell} instance. * @throws IllegalArgumentException if the variable's status is anything but * {@link jdk.jshell.Snippet.Status#VALID}. */
public String varValue(VarSnippet snippet) throws IllegalStateException { checkIfAlive(); checkValidSnippet(snippet); if (snippet.status() != Status.VALID) { throw new IllegalArgumentException( messageFormat("jshell.exc.var.not.valid", snippet, snippet.status())); } String value; try { value = executionControl().varValue(snippet.classFullName(), snippet.name()); } catch (EngineTerminationException ex) { throw new IllegalStateException(ex.getMessage()); } catch (ExecutionControlException ex) { debug(ex, "In varValue()"); return "[" + ex.getMessage() + "]"; } return expunge(value); }
Register a callback to be called when the Status of a snippet changes. Each call adds a new subscription.
Params:
  • listener – Action to perform when the Status changes.
Throws:
Returns:A token which can be used to unsubscribe this subscription.
/** * Register a callback to be called when the Status of a snippet changes. * Each call adds a new subscription. * @param listener Action to perform when the Status changes. * @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription. * @throws IllegalStateException if this {@code JShell} instance is closed. */
public Subscription onSnippetEvent(Consumer<SnippetEvent> listener) throws IllegalStateException { return onX(keyStatusListeners, listener); }
Register a callback to be called when this JShell instance terminates. This occurs either because the client process has ended (e.g. called System.exit(0)) or the connection has been shutdown, as by close(). Each call adds a new subscription.
Params:
  • listener – Action to perform when the state terminates.
Throws:
Returns:A token which can be used to unsubscribe this subscription.
/** * Register a callback to be called when this JShell instance terminates. * This occurs either because the client process has ended (e.g. called System.exit(0)) * or the connection has been shutdown, as by close(). * Each call adds a new subscription. * @param listener Action to perform when the state terminates. * @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription. * @throws IllegalStateException if this JShell instance is closed */
public Subscription onShutdown(Consumer<JShell> listener) throws IllegalStateException { return onX(shutdownListeners, listener); }
Cancel a callback subscription.
Params:
  • token – The token corresponding to the subscription to be unsubscribed.
/** * Cancel a callback subscription. * @param token The token corresponding to the subscription to be unsubscribed. */
public void unsubscribe(Subscription token) { synchronized (this) { token.remover.accept(token); } }
Subscription is a token for referring to subscriptions so they can be unsubscribed.
/** * Subscription is a token for referring to subscriptions so they can * be {@linkplain JShell#unsubscribe unsubscribed}. */
public class Subscription { Consumer<Subscription> remover; Subscription(Consumer<Subscription> remover) { this.remover = remover; } }
Provide the environment for a execution engine.
/** * Provide the environment for a execution engine. */
class ExecutionEnvImpl implements ExecutionEnv { @Override public InputStream userIn() { return in; } @Override public PrintStream userOut() { return out; } @Override public PrintStream userErr() { return err; } @Override public List<String> extraRemoteVMOptions() { return extraRemoteVMOptions; } @Override public void closeDown() { JShell.this.closeDown(); } } // --- private / package-private implementation support --- ExecutionControl executionControl() { return executionControl; } void debug(int flags, String format, Object... args) { InternalDebugControl.debug(this, err, flags, format, args); } void debug(Throwable ex, String where) { InternalDebugControl.debug(this, err, ex, where); }
Generate the next key index, indicating a unique snippet signature.
Returns:the next key index
/** * Generate the next key index, indicating a unique snippet signature. * * @return the next key index */
int nextKeyIndex() { return nextKeyIndex++; } private synchronized <T> Subscription onX(Map<Subscription, Consumer<T>> map, Consumer<T> listener) throws IllegalStateException { Objects.requireNonNull(listener); checkIfAlive(); Subscription token = new Subscription(map::remove); map.put(token, listener); return token; } private synchronized void notifyKeyStatusEvent(SnippetEvent event) { keyStatusListeners.values().forEach(l -> l.accept(event)); } private synchronized void notifyShutdownEvent(JShell state) { shutdownListeners.values().forEach(l -> l.accept(state)); } void closeDown() { if (!closed) { // Send only once closed = true; try { notifyShutdownEvent(this); } catch (Throwable thr) { // Don't care about dying exceptions } try { executionControl().close(); } catch (Throwable ex) { // don't care about exceptions on close } if (sourceCodeAnalysis != null) { sourceCodeAnalysis.close(); } InternalDebugControl.release(this); } }
Check if this JShell has been closed
Throws:
  • IllegalStateException – if it is closed
/** * Check if this JShell has been closed * @throws IllegalStateException if it is closed */
void checkIfAlive() throws IllegalStateException { if (closed) { throw new IllegalStateException(messageFormat("jshell.exc.closed", this)); } }
Check a Snippet parameter coming from the API user
Params:
  • sn – the Snippet to check
Throws:
Returns:the input Snippet (for chained calls)
/** * Check a Snippet parameter coming from the API user * @param sn the Snippet to check * @throws NullPointerException if Snippet parameter is null * @throws IllegalArgumentException if Snippet is not from this JShell * @return the input Snippet (for chained calls) */
private Snippet checkValidSnippet(Snippet sn) { if (sn == null) { throw new NullPointerException(messageFormat("jshell.exc.null")); } else { if (sn.key().state() != this || sn.id() == Snippet.UNASSOCIATED_ID) { throw new IllegalArgumentException(messageFormat("jshell.exc.alien", sn.toString())); } return sn; } }
Format using resource bundle look-up using MessageFormat
Params:
  • key – the resource key
  • args –
/** * Format using resource bundle look-up using MessageFormat * * @param key the resource key * @param args */
String messageFormat(String key, Object... args) { if (outputRB == null) { try { outputRB = ResourceBundle.getBundle(L10N_RB_NAME); } catch (MissingResourceException mre) { throw new InternalError("Cannot find ResourceBundle: " + L10N_RB_NAME); } } String s; try { s = outputRB.getString(key); } catch (MissingResourceException mre) { throw new InternalError("Missing resource: " + key + " in " + L10N_RB_NAME); } return MessageFormat.format(s, args); } }