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