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