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