All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jdk.jshell.JShell Maven / Gradle / Ivy

There is a newer version: 9-dev-r4023-3
Show newest version
/*
 * 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 {@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()}.
 * 

* An instance of {@code JShell} is created with * {@code JShell.create()}. *

* 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 tempVariableNameGenerator; final BiFunction idGenerator; final List extraRemoteVMOptions; final List extraCompilerOptions; final Function fileManagerMapping; private int nextKeyIndex = 1; final Eval eval; final ClassTracker classTracker; private final Map> shutdownListeners = new HashMap<>(); private final Map> 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 {@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: *

     * {@code
     *     JShell myShell =
     *       JShell.builder()
     *         .out(myOutStream)
     *         .err(myErrStream)
     *         .build(); } 
* 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 tempVariableNameGenerator = null; BiFunction idGenerator = null; List extraRemoteVMOptions = new ArrayList<>(); List extraCompilerOptions = new ArrayList<>(); ExecutionControlProvider executionControlProvider; Map executionControlParameters; String executionControlSpec; Function fileManagerMapping; Builder() { } /** * 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. *

* 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}. *

* 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 {@code System.out}). * The controlling process and * the remote process can share {@code System.out}. *

* 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 * {@code System.err}). The controlling process and the remote * process can share {@code System.err}. *

* 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 * {@link jdk.jshell.VarSnippet} of * {@link jdk.jshell.Snippet.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 * {@link jdk.jshell.VarSnippet#name()}. *

* 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. *

* The generated name must be unique within active snippets. *

* 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 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 * {@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}. *

* 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. *

* 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)}. *

* 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 generator) { this.idGenerator = generator; return this; } /** * 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. * * @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 {@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 {@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 executionControlParameters) { this.executionControlProvider = executionControlProvider; this.executionControlParameters = executionControlParameters; return this; } /** * 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 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 {@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 {@code JShell}. *

* 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 {@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 {@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 * {@link 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 {@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 eval(String input) throws IllegalStateException { SourceCodeAnalysisImpl a = sourceCodeAnalysis; if (a != null) { a.suspendIndexing(); } try { checkIfAlive(); List 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 * {@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 drop(Snippet snippet) throws IllegalStateException { checkIfAlive(); checkValidSnippet(snippet); List 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 * {@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 {@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. *

* If the {@link #eval(java.lang.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 {@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. */ @Override public void close() { closeDown(); } /** * Return all snippets. * @return the snippets for all current snippets in id order. */ public Stream snippets() { return maps.snippetList().stream(); } /** * 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 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 {@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 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 {@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 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 {@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 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 {@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 {@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 diagnostics(Snippet snippet) { return checkValidSnippet(snippet).diagnostics().stream(); } /** * 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 unresolvedDependencies(DeclarationSnippet snippet) { return checkValidSnippet(snippet).unresolved().stream(); } /** * 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. * @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 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. * @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 listener) throws IllegalStateException { return onX(shutdownListeners, listener); } /** * 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 {@linkplain JShell#unsubscribe unsubscribed}. */ public class Subscription { Consumer remover; Subscription(Consumer remover) { this.remover = remover; } } /** * 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 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(Exception ex, String where) { InternalDebugControl.debug(this, err, ex, where); } /** * Generate the next key index, indicating a unique snippet signature. * * @return the next key index */ int nextKeyIndex() { return nextKeyIndex++; } private synchronized Subscription onX(Map> map, Consumer 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 */ private void checkIfAlive() throws IllegalStateException { if (closed) { throw new IllegalStateException(messageFormat("jshell.exc.closed", this)); } } /** * 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) { throw new IllegalArgumentException(messageFormat("jshell.exc.alien")); } return sn; } } /** * 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); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy