1 /*
   2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.jshell;
  27 
  28 import jdk.jshell.spi.ExecutionControl;
  29 import java.io.ByteArrayInputStream;
  30 import java.io.InputStream;
  31 import java.io.PrintStream;
  32 import java.text.MessageFormat;
  33 import java.util.ArrayList;
  34 import java.util.Arrays;
  35 import java.util.Collections;
  36 import java.util.HashMap;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.MissingResourceException;
  40 import java.util.Objects;
  41 import java.util.ResourceBundle;
  42 import java.util.function.BiFunction;
  43 import java.util.function.Consumer;
  44 
  45 import java.util.function.Supplier;
  46 import java.util.stream.Stream;
  47 import jdk.internal.jshell.debug.InternalDebugControl;
  48 import jdk.jshell.Snippet.Status;
  49 import jdk.jshell.execution.JDIDefaultExecutionControl;
  50 import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
  51 import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
  52 import jdk.jshell.spi.ExecutionEnv;
  53 import static jdk.jshell.execution.Util.failOverExecutionControlGenerator;
  54 import static java.util.stream.Collectors.collectingAndThen;
  55 import static java.util.stream.Collectors.toList;
  56 import static jdk.jshell.Util.expunge;
  57 
  58 /**
  59  * The JShell evaluation state engine.  This is the central class in the JShell
  60  * API.  A {@code JShell} instance holds the evolving compilation and
  61  * execution state.  The state is changed with the instance methods
  62  * {@link jdk.jshell.JShell#eval(java.lang.String) eval(String)},
  63  * {@link jdk.jshell.JShell#drop(jdk.jshell.PersistentSnippet) drop(PersistentSnippet)} and
  64  * {@link jdk.jshell.JShell#addToClasspath(java.lang.String) addToClasspath(String)}.
  65  * The majority of methods query the state.
  66  * A {@code JShell} instance also allows registering for events with
  67  * {@link jdk.jshell.JShell#onSnippetEvent(java.util.function.Consumer) onSnippetEvent(Consumer)}
  68  * and {@link jdk.jshell.JShell#onShutdown(java.util.function.Consumer) onShutdown(Consumer)}, which
  69  * are unregistered with
  70  * {@link jdk.jshell.JShell#unsubscribe(jdk.jshell.JShell.Subscription) unsubscribe(Subscription)}.
  71  * Access to the source analysis utilities is via
  72  * {@link jdk.jshell.JShell#sourceCodeAnalysis()}.
  73  * When complete the instance should be closed to free resources --
  74  * {@link jdk.jshell.JShell#close()}.
  75  * <p>
  76  * An instance of {@code JShell} is created with
  77  * {@code JShell.create()}.
  78  * <p>
  79  * This class is not thread safe, except as noted, all access should be through
  80  * a single thread.
  81  * @author Robert Field
  82  */
  83 public class JShell implements AutoCloseable {
  84 
  85     final SnippetMaps maps;
  86     final KeyMap keyMap;
  87     final OuterWrapMap outerMap;
  88     final TaskFactory taskFactory;
  89     final InputStream in;
  90     final PrintStream out;
  91     final PrintStream err;
  92     final Supplier<String> tempVariableNameGenerator;
  93     final BiFunction<Snippet, Integer, String> idGenerator;
  94     final List<String> extraRemoteVMOptions;
  95     final List<String> extraCompilerOptions;
  96     final ExecutionControl.Generator executionControlGenerator;
  97 
  98     private int nextKeyIndex = 1;
  99 
 100     final Eval eval;
 101     final ClassTracker classTracker;
 102     private final Map<Subscription, Consumer<JShell>> shutdownListeners = new HashMap<>();
 103     private final Map<Subscription, Consumer<SnippetEvent>> keyStatusListeners = new HashMap<>();
 104     private boolean closed = false;
 105 
 106     private ExecutionControl executionControl = null;
 107     private SourceCodeAnalysisImpl sourceCodeAnalysis = null;
 108 
 109     private static final String L10N_RB_NAME    = "jdk.jshell.resources.l10n";
 110     private static ResourceBundle outputRB  = null;
 111 
 112     JShell(Builder b) {
 113         this.in = b.in;
 114         this.out = b.out;
 115         this.err = b.err;
 116         this.tempVariableNameGenerator = b.tempVariableNameGenerator;
 117         this.idGenerator = b.idGenerator;
 118         this.extraRemoteVMOptions = b.extraRemoteVMOptions;
 119         this.extraCompilerOptions = b.extraCompilerOptions;
 120         this.executionControlGenerator = b.executionControlGenerator==null
 121                 ? failOverExecutionControlGenerator(
 122                         JDIDefaultExecutionControl.launch(),
 123                         JDIDefaultExecutionControl.listenLocalhost(),
 124                         JDIDefaultExecutionControl.listen())
 125                 : b.executionControlGenerator;
 126 
 127         this.maps = new SnippetMaps(this);
 128         this.keyMap = new KeyMap(this);
 129         this.outerMap = new OuterWrapMap(this);
 130         this.taskFactory = new TaskFactory(this);
 131         this.eval = new Eval(this);
 132         this.classTracker = new ClassTracker();
 133     }
 134 
 135     /**
 136      * Builder for {@code JShell} instances.
 137      * Create custom instances of {@code JShell} by using the setter
 138      * methods on this class.  After zero or more of these, use the
 139      * {@link #build()} method to create a {@code JShell} instance.
 140      * These can all be chained. For example, setting the remote output and
 141      * error streams:
 142      * <pre>
 143      * {@code
 144      *     JShell myShell =
 145      *       JShell.builder()
 146      *         .out(myOutStream)
 147      *         .err(myErrStream)
 148      *         .build(); } </pre>
 149      * If no special set-up is needed, just use
 150      * {@code JShell.builder().build()} or the short-cut equivalent
 151      * {@code JShell.create()}.
 152      */
 153     public static class Builder {
 154 
 155         InputStream in = new ByteArrayInputStream(new byte[0]);
 156         PrintStream out = System.out;
 157         PrintStream err = System.err;
 158         Supplier<String> tempVariableNameGenerator = null;
 159         BiFunction<Snippet, Integer, String> idGenerator = null;
 160         List<String> extraRemoteVMOptions = new ArrayList<>();
 161         List<String> extraCompilerOptions = new ArrayList<>();
 162         ExecutionControl.Generator executionControlGenerator;
 163 
 164         Builder() { }
 165 
 166         /**
 167          * Sets the input for the running evaluation (it's {@code System.in}). Note:
 168          * applications that use {@code System.in} for snippet or other
 169          * user input cannot use {@code System.in} as the input stream for
 170          * the remote process.
 171          * <p>
 172          * The default, if this is not set, is to provide an empty input stream
 173          * -- {@code new ByteArrayInputStream(new byte[0])}.
 174          *
 175          * @param in the {@code InputStream} to be channelled to
 176          * {@code System.in} in the remote execution process
 177          * @return the {@code Builder} instance (for use in chained
 178          * initialization)
 179          */
 180         public Builder in(InputStream in) {
 181             this.in = in;
 182             return this;
 183         }
 184 
 185         /**
 186          * Sets the output for the running evaluation (it's {@code System.out}).
 187          * The controlling process and
 188          * the remote process can share {@code System.out}.
 189          * <p>
 190          * The default, if this is not set, is {@code System.out}.
 191          *
 192          * @param out the {@code PrintStream} to be channelled to
 193          * {@code System.out} in the remote execution process
 194          * @return the {@code Builder} instance (for use in chained
 195          * initialization)
 196          */
 197         public Builder out(PrintStream out) {
 198             this.out = out;
 199             return this;
 200         }
 201 
 202         /**
 203          * Sets the error output for the running evaluation (it's
 204          * {@code System.err}). The controlling process and the remote
 205          * process can share {@code System.err}.
 206          * <p>
 207          * The default, if this is not set, is {@code System.err}.
 208          *
 209          * @param err the {@code PrintStream} to be channelled to
 210          * {@code System.err} in the remote execution process
 211          * @return the {@code Builder} instance (for use in chained
 212          * initialization)
 213          */
 214         public Builder err(PrintStream err) {
 215             this.err = err;
 216             return this;
 217         }
 218 
 219         /**
 220          * Sets a generator of temp variable names for
 221          * {@link jdk.jshell.VarSnippet} of
 222          * {@link jdk.jshell.Snippet.SubKind#TEMP_VAR_EXPRESSION_SUBKIND}.
 223          * <p>
 224          * Do not use this method unless you have explicit need for it.
 225          * <p>
 226          * The generator will be used for newly created VarSnippet
 227          * instances. The name of a variable is queried with
 228          * {@link jdk.jshell.VarSnippet#name()}.
 229          * <p>
 230          * The callback is sent during the processing of the snippet, the
 231          * JShell state is not stable. No calls whatsoever on the
 232          * {@code JShell} instance may be made from the callback.
 233          * <p>
 234          * The generated name must be unique within active snippets.
 235          * <p>
 236          * The default behavior (if this is not set or {@code generator}
 237          * is null) is to generate the name as a sequential number with a
 238          * prefixing dollar sign ("$").
 239          *
 240          * @param generator the {@code Supplier} to generate the temporary
 241          * variable name string or {@code null}
 242          * @return the {@code Builder} instance (for use in chained
 243          * initialization)
 244          */
 245         public Builder tempVariableNameGenerator(Supplier<String> generator) {
 246             this.tempVariableNameGenerator = generator;
 247             return this;
 248         }
 249 
 250         /**
 251          * Sets the generator of identifying names for Snippets.
 252          * <p>
 253          * Do not use this method unless you have explicit need for it.
 254          * <p>
 255          * The generator will be used for newly created Snippet instances. The
 256          * identifying name (id) is accessed with
 257          * {@link jdk.jshell.Snippet#id()} and can be seen in the
 258          * {@code StackTraceElement.getFileName()} for a
 259          * {@link jdk.jshell.EvalException} and
 260          * {@link jdk.jshell.UnresolvedReferenceException}.
 261          * <p>
 262          * The inputs to the generator are the {@link jdk.jshell.Snippet} and an
 263          * integer. The integer will be the same for two Snippets which would
 264          * overwrite one-another, but otherwise is unique.
 265          * <p>
 266          * The callback is sent during the processing of the snippet and the
 267          * Snippet and the state as a whole are not stable. No calls to change
 268          * system state (including Snippet state) should be made. Queries of
 269          * Snippet may be made except to {@link jdk.jshell.Snippet#id()}. No
 270          * calls on the {@code JShell} instance may be made from the
 271          * callback, except to
 272          * {@link #status(jdk.jshell.Snippet) status(Snippet)}.
 273          * <p>
 274          * The default behavior (if this is not set or {@code generator}
 275          * is null) is to generate the id as the integer converted to a string.
 276          *
 277          * @param generator the {@code BiFunction} to generate the id
 278          * string or {@code null}
 279          * @return the {@code Builder} instance (for use in chained
 280          * initialization)
 281          */
 282         public Builder idGenerator(BiFunction<Snippet, Integer, String> generator) {
 283             this.idGenerator = generator;
 284             return this;
 285         }
 286 
 287         /**
 288          * Sets additional VM options for launching the VM.
 289          *
 290          * @param options The options for the remote VM
 291          * @return the {@code Builder} instance (for use in chained
 292          * initialization)
 293          */
 294         public Builder remoteVMOptions(String... options) {
 295             this.extraRemoteVMOptions.addAll(Arrays.asList(options));
 296             return this;
 297         }
 298 
 299         /**
 300          * Adds compiler options.  These additional options will be used on
 301          * parsing, analysis, and code generation calls to the compiler.
 302          * Options which interfere with results are not supported and have
 303          * undefined effects on JShell's operation.
 304          *
 305          * @param options the addition options for compiler invocations
 306          * @return the {@code Builder} instance (for use in chained
 307          * initialization)
 308          */
 309         public Builder compilerOptions(String... options) {
 310             this.extraCompilerOptions.addAll(Arrays.asList(options));
 311             return this;
 312         }
 313 
 314         /**
 315          * Sets the custom engine for execution. Snippet execution will be
 316          * provided by the specified {@link ExecutionControl} instance.
 317          *
 318          * @param executionControlGenerator the execution engine generator
 319          * @return the {@code Builder} instance (for use in chained
 320          * initialization)
 321          */
 322         public Builder executionEngine(ExecutionControl.Generator executionControlGenerator) {
 323             this.executionControlGenerator = executionControlGenerator;
 324             return this;
 325         }
 326 
 327         /**
 328          * Builds a JShell state engine. This is the entry-point to all JShell
 329          * functionality. This creates a remote process for execution. It is
 330          * thus important to close the returned instance.
 331          *
 332          * @return the state engine
 333          */
 334         public JShell build() {
 335             return new JShell(this);
 336         }
 337     }
 338 
 339     // --- public API ---
 340 
 341     /**
 342      * Create a new JShell state engine.
 343      * That is, create an instance of {@code JShell}.
 344      * <p>
 345      * Equivalent to {@link JShell#builder() JShell.builder()}{@link JShell.Builder#build() .build()}.
 346      * @return an instance of {@code JShell}.
 347      */
 348     public static JShell create() {
 349         return builder().build();
 350     }
 351 
 352     /**
 353      * Factory method for {@code JShell.Builder} which, in-turn, is used
 354      * for creating instances of {@code JShell}.
 355      * Create a default instance of {@code JShell} with
 356      * {@code JShell.builder().build()}. For more construction options
 357      * see {@link jdk.jshell.JShell.Builder}.
 358      * @return an instance of {@code Builder}.
 359      * @see jdk.jshell.JShell.Builder
 360      */
 361     public static Builder builder() {
 362         return new Builder();
 363     }
 364 
 365     /**
 366      * Access to source code analysis functionality.
 367      * An instance of {@code JShell} will always return the same
 368      * {@code SourceCodeAnalysis} instance from
 369      * {@code sourceCodeAnalysis()}.
 370      * @return an instance of {@link SourceCodeAnalysis SourceCodeAnalysis}
 371      * which can be used for source analysis such as completion detection and
 372      * completion suggestions.
 373      */
 374     public SourceCodeAnalysis sourceCodeAnalysis() {
 375         if (sourceCodeAnalysis == null) {
 376             sourceCodeAnalysis = new SourceCodeAnalysisImpl(this);
 377         }
 378         return sourceCodeAnalysis;
 379     }
 380 
 381     /**
 382      * Evaluate the input String, including definition and/or execution, if
 383      * applicable. The input is checked for errors, unless the errors can be
 384      * deferred (as is the case with some unresolvedDependencies references),
 385      * errors will abort evaluation.
 386      * <p>
 387      * The input should be
 388      * exactly one complete snippet of source code, that is, one expression,
 389      * statement, variable declaration, method declaration, class declaration,
 390      * or import.
 391      * To break arbitrary input into individual complete snippets, use
 392      * {@link SourceCodeAnalysis#analyzeCompletion(String)}.
 393      * <p>
 394      * For imports, the import is added.  Classes, interfaces. methods,
 395      * and variables are defined.  The initializer of variables, statements,
 396      * and expressions are executed.
 397      * The modifiers public, protected, private, static, and final are not
 398      * allowed on op-level declarations and are ignored with a warning.
 399      * Synchronized, native, abstract, and default top-level methods are not
 400      * allowed and are errors.
 401      * If a previous definition of a declaration is overwritten then there will
 402      * be an event showing its status changed to OVERWRITTEN, this will not
 403      * occur for dropped, rejected, or already overwritten declarations.
 404      * <p>
 405      * If execution environment is out of process, as is the default case, then
 406      * if the evaluated code
 407      * causes the execution environment to terminate, this {@code JShell}
 408      * instance will be closed but the calling process and VM remain valid.
 409      * @param input The input String to evaluate
 410      * @return the list of events directly or indirectly caused by this evaluation.
 411      * @throws IllegalStateException if this {@code JShell} instance is closed.
 412      * @see SourceCodeAnalysis#analyzeCompletion(String)
 413      * @see JShell#onShutdown(java.util.function.Consumer)
 414      */
 415     public List<SnippetEvent> eval(String input) throws IllegalStateException {
 416         SourceCodeAnalysisImpl a = sourceCodeAnalysis;
 417         if (a != null) {
 418             a.suspendIndexing();
 419         }
 420         try {
 421             checkIfAlive();
 422             List<SnippetEvent> events = eval.eval(input);
 423             events.forEach(this::notifyKeyStatusEvent);
 424             return Collections.unmodifiableList(events);
 425         } finally {
 426             if (a != null) {
 427                 a.resumeIndexing();
 428             }
 429         }
 430     }
 431 
 432     /**
 433      * Remove a declaration from the state.
 434      * @param snippet The snippet to remove
 435      * @return The list of events from updating declarations dependent on the
 436      * dropped snippet.
 437      * @throws IllegalStateException if this {@code JShell} instance is closed.
 438      * @throws IllegalArgumentException if the snippet is not associated with
 439      * this {@code JShell} instance.
 440      */
 441     public List<SnippetEvent> drop(PersistentSnippet snippet) throws IllegalStateException {
 442         checkIfAlive();
 443         checkValidSnippet(snippet);
 444         List<SnippetEvent> events = eval.drop(snippet);
 445         events.forEach(this::notifyKeyStatusEvent);
 446         return Collections.unmodifiableList(events);
 447     }
 448 
 449     /**
 450      * The specified path is added to the end of the classpath used in eval().
 451      * Note that the unnamed package is not accessible from the package in which
 452      * {@link JShell#eval(String)} code is placed.
 453      * @param path the path to add to the classpath.
 454      */
 455     public void addToClasspath(String path) {
 456         // Compiler
 457         taskFactory.addToClasspath(path);
 458         // Runtime
 459         try {
 460             executionControl().addToClasspath(path);
 461         } catch (ExecutionControlException ex) {
 462             debug(ex, "on addToClasspath(" + path + ")");
 463         }
 464         if (sourceCodeAnalysis != null) {
 465             sourceCodeAnalysis.classpathChanged();
 466         }
 467     }
 468 
 469     /**
 470      * Attempt to stop currently running evaluation. When called while
 471      * the {@link #eval(java.lang.String) } method is running and the
 472      * user's code being executed, an attempt will be made to stop user's code.
 473      * Note that typically this method needs to be called from a different thread
 474      * than the one running the {@code eval} method.
 475      * <p>
 476      * If the {@link #eval(java.lang.String) } method is not running, does nothing.
 477      * <p>
 478      * The attempt to stop the user's code may fail in some case, which may include
 479      * when the execution is blocked on an I/O operation, or when the user's code is
 480      * catching the {@link ThreadDeath} exception.
 481      */
 482     public void stop() {
 483         if (executionControl != null) {
 484             try {
 485                 executionControl.stop();
 486             } catch (ExecutionControlException ex) {
 487                 debug(ex, "on stop()");
 488             }
 489         }
 490     }
 491 
 492     /**
 493      * Close this state engine. Frees resources. Should be called when this
 494      * state engine is no longer needed.
 495      */
 496     @Override
 497     public void close() {
 498         if (!closed) {
 499             closeDown();
 500             executionControl().close();
 501         }
 502     }
 503 
 504     /**
 505      * Return all snippets.
 506      * @return the snippets for all current snippets in id order.
 507      * @throws IllegalStateException if this JShell instance is closed.
 508      */
 509     public Stream<Snippet> snippets() throws IllegalStateException {
 510         checkIfAlive();
 511         return maps.snippetList().stream();
 512     }
 513 
 514     /**
 515      * Returns the active variable snippets.
 516      * This convenience method is equivalent to {@code snippets()} filtered for
 517      * {@link jdk.jshell.Snippet.Status#isActive() status(snippet).isActive()}
 518      * {@code && snippet.kind() == Kind.VARIABLE}
 519      * and cast to {@code VarSnippet}.
 520      * @return the active declared variables.
 521      * @throws IllegalStateException if this JShell instance is closed.
 522      */
 523     public Stream<VarSnippet> variables() throws IllegalStateException {
 524         return snippets()
 525                      .filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.VAR)
 526                      .map(sn -> (VarSnippet) sn);
 527     }
 528 
 529     /**
 530      * Returns the active method snippets.
 531      * This convenience method is equivalent to {@code snippets()} filtered for
 532      * {@link jdk.jshell.Snippet.Status#isActive() status(snippet).isActive()}
 533      * {@code && snippet.kind() == Kind.METHOD}
 534      * and cast to MethodSnippet.
 535      * @return the active declared methods.
 536      * @throws IllegalStateException if this JShell instance is closed.
 537      */
 538     public Stream<MethodSnippet> methods() throws IllegalStateException {
 539         return snippets()
 540                      .filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.METHOD)
 541                      .map(sn -> (MethodSnippet)sn);
 542     }
 543 
 544     /**
 545      * Returns the active type declaration (class, interface, annotation type, and enum) snippets.
 546      * This convenience method is equivalent to {@code snippets()} filtered for
 547      * {@link jdk.jshell.Snippet.Status#isActive() status(snippet).isActive()}
 548      * {@code && snippet.kind() == Kind.TYPE_DECL}
 549      * and cast to TypeDeclSnippet.
 550      * @return the active declared type declarations.
 551      * @throws IllegalStateException if this JShell instance is closed.
 552      */
 553     public Stream<TypeDeclSnippet> types() throws IllegalStateException {
 554         return snippets()
 555                 .filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.TYPE_DECL)
 556                 .map(sn -> (TypeDeclSnippet) sn);
 557     }
 558 
 559     /**
 560      * Returns the active import snippets.
 561      * This convenience method is equivalent to {@code snippets()} filtered for
 562      * {@link jdk.jshell.Snippet.Status#isActive() status(snippet).isActive()}
 563      * {@code && snippet.kind() == Kind.IMPORT}
 564      * and cast to ImportSnippet.
 565      * @return the active declared import declarations.
 566      * @throws IllegalStateException if this JShell instance is closed.
 567      */
 568     public Stream<ImportSnippet> imports() throws IllegalStateException {
 569         return snippets()
 570                 .filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.IMPORT)
 571                 .map(sn -> (ImportSnippet) sn);
 572     }
 573 
 574     /**
 575      * Return the status of the snippet.
 576      * This is updated either because of an explicit {@code eval()} call or
 577      * an automatic update triggered by a dependency.
 578      * @param snippet the {@code Snippet} to look up
 579      * @return the status corresponding to this snippet
 580      * @throws IllegalStateException if this {@code JShell} instance is closed.
 581      * @throws IllegalArgumentException if the snippet is not associated with
 582      * this {@code JShell} instance.
 583      */
 584     public Status status(Snippet snippet) {
 585         return checkValidSnippet(snippet).status();
 586     }
 587 
 588     /**
 589      * Return the diagnostics of the most recent evaluation of the snippet.
 590      * The evaluation can either because of an explicit {@code eval()} call or
 591      * an automatic update triggered by a dependency.
 592      * @param snippet the {@code Snippet} to look up
 593      * @return the diagnostics corresponding to this snippet.  This does not
 594      * include unresolvedDependencies references reported in {@code unresolvedDependencies()}.
 595      * @throws IllegalStateException if this {@code JShell} instance is closed.
 596      * @throws IllegalArgumentException if the snippet is not associated with
 597      * this {@code JShell} instance.
 598      */
 599     public Stream<Diag> diagnostics(Snippet snippet) {
 600         return checkValidSnippet(snippet).diagnostics().stream();
 601     }
 602 
 603     /**
 604      * For {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED} or
 605      * {@link jdk.jshell.Snippet.Status#RECOVERABLE_NOT_DEFINED RECOVERABLE_NOT_DEFINED}
 606      * declarations, the names of current unresolved dependencies for
 607      * the snippet.
 608      * The returned value of this method, for a given method may change when an
 609      * {@code eval()} or {@code drop()} of another snippet causes
 610      * an update of a dependency.
 611      * @param snippet the declaration {@code Snippet} to look up
 612      * @return a stream of symbol names that are currently unresolvedDependencies.
 613      * @throws IllegalStateException if this {@code JShell} instance is closed.
 614      * @throws IllegalArgumentException if the snippet is not associated with
 615      * this {@code JShell} instance.
 616      */
 617     public Stream<String> unresolvedDependencies(DeclarationSnippet snippet) {
 618         return checkValidSnippet(snippet).unresolved().stream();
 619     }
 620 
 621     /**
 622      * Get the current value of a variable.
 623      * @param snippet the variable Snippet whose value is queried.
 624      * @return the current value of the variable referenced by snippet.
 625      * @throws IllegalStateException if this {@code JShell} instance is closed.
 626      * @throws IllegalArgumentException if the snippet is not associated with
 627      * this {@code JShell} instance.
 628      * @throws IllegalArgumentException if the variable's status is anything but
 629      * {@link jdk.jshell.Snippet.Status#VALID}.
 630      */
 631     public String varValue(VarSnippet snippet) throws IllegalStateException {
 632         checkIfAlive();
 633         checkValidSnippet(snippet);
 634         if (snippet.status() != Status.VALID) {
 635             throw new IllegalArgumentException(
 636                     messageFormat("jshell.exc.var.not.valid",  snippet, snippet.status()));
 637         }
 638         String value;
 639         try {
 640             value = executionControl().varValue(snippet.classFullName(), snippet.name());
 641         } catch (EngineTerminationException ex) {
 642             throw new IllegalStateException(ex.getMessage());
 643         } catch (ExecutionControlException ex) {
 644             debug(ex, "In varValue()");
 645             return "[" + ex.getMessage() + "]";
 646         }
 647         return expunge(value);
 648     }
 649 
 650     /**
 651      * Register a callback to be called when the Status of a snippet changes.
 652      * Each call adds a new subscription.
 653      * @param listener Action to perform when the Status changes.
 654      * @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription.
 655      * @throws IllegalStateException if this {@code JShell} instance is closed.
 656      */
 657     public Subscription onSnippetEvent(Consumer<SnippetEvent> listener)
 658             throws IllegalStateException {
 659         return onX(keyStatusListeners, listener);
 660     }
 661 
 662     /**
 663      * Register a callback to be called when this JShell instance terminates.
 664      * This occurs either because the client process has ended (e.g. called System.exit(0))
 665      * or the connection has been shutdown, as by close().
 666      * Each call adds a new subscription.
 667      * @param listener Action to perform when the state terminates.
 668      * @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription.
 669      * @throws IllegalStateException if this JShell instance is closed
 670      */
 671     public Subscription onShutdown(Consumer<JShell> listener)
 672             throws IllegalStateException {
 673         return onX(shutdownListeners, listener);
 674     }
 675 
 676     /**
 677      * Cancel a callback subscription.
 678      * @param token The token corresponding to the subscription to be unsubscribed.
 679      */
 680     public void unsubscribe(Subscription token) {
 681         synchronized (this) {
 682             token.remover.accept(token);
 683         }
 684     }
 685 
 686     /**
 687      * Subscription is a token for referring to subscriptions so they can
 688      * be {@linkplain JShell#unsubscribe unsubscribed}.
 689      */
 690     public class Subscription {
 691 
 692         Consumer<Subscription> remover;
 693 
 694         Subscription(Consumer<Subscription> remover) {
 695             this.remover = remover;
 696         }
 697     }
 698 
 699     /**
 700      * Provide the environment for a execution engine.
 701      */
 702     class ExecutionEnvImpl implements ExecutionEnv {
 703 
 704         @Override
 705         public InputStream userIn() {
 706             return in;
 707         }
 708 
 709         @Override
 710         public PrintStream userOut() {
 711             return out;
 712         }
 713 
 714         @Override
 715         public PrintStream userErr() {
 716             return err;
 717         }
 718 
 719         @Override
 720         public List<String> extraRemoteVMOptions() {
 721             return extraRemoteVMOptions;
 722         }
 723 
 724         @Override
 725         public void closeDown() {
 726             JShell.this.closeDown();
 727         }
 728 
 729     }
 730 
 731     // --- private / package-private implementation support ---
 732     ExecutionControl executionControl() {
 733         if (executionControl == null) {
 734             try {
 735                 executionControl =  executionControlGenerator.generate(new ExecutionEnvImpl());
 736             } catch (Throwable ex) {
 737                 throw new InternalError("Launching execution engine threw: " + ex.getMessage(), ex);
 738             }
 739         }
 740         return executionControl;
 741     }
 742 
 743     void debug(int flags, String format, Object... args) {
 744         InternalDebugControl.debug(this, err, flags, format, args);
 745     }
 746 
 747     void debug(Exception ex, String where) {
 748         InternalDebugControl.debug(this, err, ex, where);
 749     }
 750 
 751     /**
 752      * Generate the next key index, indicating a unique snippet signature.
 753      *
 754      * @return the next key index
 755      */
 756     int nextKeyIndex() {
 757         return nextKeyIndex++;
 758     }
 759 
 760     private synchronized <T> Subscription onX(Map<Subscription, Consumer<T>> map, Consumer<T> listener)
 761             throws IllegalStateException {
 762         Objects.requireNonNull(listener);
 763         checkIfAlive();
 764         Subscription token = new Subscription(map::remove);
 765         map.put(token, listener);
 766         return token;
 767     }
 768 
 769     private synchronized void notifyKeyStatusEvent(SnippetEvent event) {
 770         keyStatusListeners.values().forEach(l -> l.accept(event));
 771     }
 772 
 773     private synchronized void notifyShutdownEvent(JShell state) {
 774         shutdownListeners.values().forEach(l -> l.accept(state));
 775     }
 776 
 777     void closeDown() {
 778         if (!closed) {
 779             // Send only once
 780             closed = true;
 781             try {
 782                 notifyShutdownEvent(this);
 783             } catch (Throwable thr) {
 784                 // Don't care about dying exceptions
 785             }
 786         }
 787     }
 788 
 789     /**
 790      * Check if this JShell has been closed
 791      * @throws IllegalStateException if it is closed
 792      */
 793     private void checkIfAlive()  throws IllegalStateException {
 794         if (closed) {
 795             throw new IllegalStateException(messageFormat("jshell.exc.closed", this));
 796         }
 797     }
 798 
 799     /**
 800      * Check a Snippet parameter coming from the API user
 801      * @param sn the Snippet to check
 802      * @throws NullPointerException if Snippet parameter is null
 803      * @throws IllegalArgumentException if Snippet is not from this JShell
 804      * @return the input Snippet (for chained calls)
 805      */
 806     private Snippet checkValidSnippet(Snippet sn) {
 807         if (sn == null) {
 808             throw new NullPointerException(messageFormat("jshell.exc.null"));
 809         } else {
 810             if (sn.key().state() != this) {
 811                 throw new IllegalArgumentException(messageFormat("jshell.exc.alien"));
 812             }
 813             return sn;
 814         }
 815     }
 816 
 817     /**
 818      * Format using resource bundle look-up using MessageFormat
 819      *
 820      * @param key the resource key
 821      * @param args
 822      */
 823     String messageFormat(String key, Object... args) {
 824         if (outputRB == null) {
 825             try {
 826                 outputRB = ResourceBundle.getBundle(L10N_RB_NAME);
 827             } catch (MissingResourceException mre) {
 828                 throw new InternalError("Cannot find ResourceBundle: " + L10N_RB_NAME);
 829             }
 830         }
 831         String s;
 832         try {
 833             s = outputRB.getString(key);
 834         } catch (MissingResourceException mre) {
 835             throw new InternalError("Missing resource: " + key + " in " + L10N_RB_NAME);
 836         }
 837         return MessageFormat.format(s, args);
 838     }
 839 
 840 }