JShell.java revision 3095:cf000bae9c31
1262395Sbapt/*
2262395Sbapt * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3262395Sbapt * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4262395Sbapt *
5262395Sbapt * This code is free software; you can redistribute it and/or modify it
6262395Sbapt * under the terms of the GNU General Public License version 2 only, as
7262395Sbapt * published by the Free Software Foundation.  Oracle designates this
8262395Sbapt * particular file as subject to the "Classpath" exception as provided
9262395Sbapt * by Oracle in the LICENSE file that accompanied this code.
10262395Sbapt *
11262395Sbapt * This code is distributed in the hope that it will be useful, but WITHOUT
12262395Sbapt * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13262395Sbapt * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14262395Sbapt * version 2 for more details (a copy is included in the LICENSE file that
15262395Sbapt * accompanied this code).
16262395Sbapt *
17262395Sbapt * You should have received a copy of the GNU General Public License version
18262395Sbapt * 2 along with this work; if not, write to the Free Software Foundation,
19262395Sbapt * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20262395Sbapt *
21262395Sbapt * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22262395Sbapt * or visit www.oracle.com if you need additional information or have any
23262395Sbapt * questions.
24262395Sbapt */
25262395Sbapt
26262395Sbaptpackage jdk.jshell;
27262395Sbapt
28262395Sbaptimport java.io.ByteArrayInputStream;
29262395Sbaptimport java.io.IOException;
30262395Sbaptimport java.io.InputStream;
31262395Sbaptimport java.io.PrintStream;
32262395Sbaptimport java.util.ArrayList;
33262395Sbaptimport java.util.Collections;
34268896Sbaptimport java.util.HashMap;
35262395Sbaptimport java.util.List;
36262395Sbaptimport java.util.Map;
37262395Sbaptimport java.util.Objects;
38262395Sbaptimport java.util.function.BiFunction;
39262395Sbaptimport java.util.function.Consumer;
40262395Sbapt
41262395Sbaptimport java.util.function.Supplier;
42262395Sbaptimport jdk.internal.jshell.debug.InternalDebugControl;
43262395Sbaptimport static java.util.stream.Collectors.collectingAndThen;
44262395Sbaptimport static java.util.stream.Collectors.toList;
45262395Sbaptimport static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
46262395Sbaptimport static jdk.jshell.Util.expunge;
47262395Sbaptimport jdk.jshell.Snippet.Status;
48262395Sbapt
49262395Sbapt/**
50262395Sbapt * The JShell evaluation state engine.  This is the central class in the JShell
51262395Sbapt * API.  A <code>JShell</code> instance holds the evolving compilation and
52262395Sbapt * execution state.  The state is changed with the instance methods
53262395Sbapt * {@link jdk.jshell.JShell#eval(java.lang.String) eval(String)},
54262395Sbapt * {@link jdk.jshell.JShell#drop(jdk.jshell.PersistentSnippet) drop(PersistentSnippet)} and
55262395Sbapt * {@link jdk.jshell.JShell#addToClasspath(java.lang.String) addToClasspath(String)}.
56262395Sbapt * The majority of methods query the state.
57262395Sbapt * A <code>JShell</code> instance also allows registering for events with
58262395Sbapt * {@link jdk.jshell.JShell#onSnippetEvent(java.util.function.Consumer) onSnippetEvent(Consumer)}
59262395Sbapt * and {@link jdk.jshell.JShell#onShutdown(java.util.function.Consumer) onShutdown(Consumer)}, which
60262395Sbapt * are unregistered with
61262395Sbapt * {@link jdk.jshell.JShell#unsubscribe(jdk.jshell.JShell.Subscription) unsubscribe(Subscription)}.
62262395Sbapt * Access to the source analysis utilities is via
63262395Sbapt * {@link jdk.jshell.JShell#sourceCodeAnalysis()}.
64262395Sbapt * When complete the instance should be closed to free resources --
65268896Sbapt * {@link jdk.jshell.JShell#close()}.
66268896Sbapt * <p>
67262395Sbapt * An instance of <code>JShell</code> is created with
68262395Sbapt * <code>JShell.create()</code>.
69262395Sbapt * <p>
70262395Sbapt * This class is not thread safe, except as noted, all access should be through
71268896Sbapt * a single thread.
72262395Sbapt * @see jdk.jshell
73262395Sbapt * @author Robert Field
74262395Sbapt */
75262395Sbaptpublic class JShell implements AutoCloseable {
76268896Sbapt
77268896Sbapt    final SnippetMaps maps;
78262395Sbapt    final KeyMap keyMap;
79262395Sbapt    final TaskFactory taskFactory;
80262395Sbapt    final InputStream in;
81262395Sbapt    final PrintStream out;
82262395Sbapt    final PrintStream err;
83262395Sbapt    final Supplier<String> tempVariableNameGenerator;
84262395Sbapt    final BiFunction<Snippet, Integer, String> idGenerator;
85262395Sbapt
86268896Sbapt    private int nextKeyIndex = 1;
87262395Sbapt
88262395Sbapt    final Eval eval;
89262395Sbapt    final ClassTracker classTracker;
90262395Sbapt    private final Map<Subscription, Consumer<JShell>> shutdownListeners = new HashMap<>();
91262395Sbapt    private final Map<Subscription, Consumer<SnippetEvent>> keyStatusListeners = new HashMap<>();
92262395Sbapt    private boolean closed = false;
93262395Sbapt
94
95    private ExecutionControl executionControl = null;
96    private SourceCodeAnalysis sourceCodeAnalysis = null;
97
98
99    JShell(Builder b) {
100        this.in = b.in;
101        this.out = b.out;
102        this.err = b.err;
103        this.tempVariableNameGenerator = b.tempVariableNameGenerator;
104        this.idGenerator = b.idGenerator;
105
106        this.maps = new SnippetMaps(this);
107        maps.setPackageName("REPL");
108        this.keyMap = new KeyMap(this);
109        this.taskFactory = new TaskFactory(this);
110        this.eval = new Eval(this);
111        this.classTracker = new ClassTracker(this);
112    }
113
114    /**
115     * Builder for <code>JShell</code> instances.
116     * Create custom instances of <code>JShell</code> by using the setter
117     * methods on this class.  After zero or more of these, use the
118     * {@link #build()} method to create a <code>JShell</code> instance.
119     * These can all be chained. For example, setting the remote output and
120     * error streams:
121     * <pre>
122     * <code>
123     *     JShell myShell =
124     *       JShell.builder()
125     *         .out(myOutStream)
126     *         .err(myErrStream)
127     *         .build(); </code> </pre>
128     * If no special set-up is needed, just use
129     * <code>JShell.builder().build()</code> or the short-cut equivalent
130     * <code>JShell.create()</code>.
131     */
132    public static class Builder {
133
134        InputStream in = new ByteArrayInputStream(new byte[0]);
135        PrintStream out = System.out;
136        PrintStream err = System.err;
137        Supplier<String> tempVariableNameGenerator = null;
138        BiFunction<Snippet, Integer, String> idGenerator = null;
139
140        Builder() { }
141
142        /**
143         * Input for the running evaluation (it's <code>System.in</code>). Note:
144         * applications that use <code>System.in</code> for snippet or other
145         * user input cannot use <code>System.in</code> as the input stream for
146         * the remote process.
147         * <p>
148         * The default, if this is not set, is to provide an empty input stream
149         * -- <code>new ByteArrayInputStream(new byte[0])</code>.
150         *
151         * @param in the <code>InputStream</code> to be channelled to
152         * <code>System.in</code> in the remote execution process.
153         * @return the <code>Builder</code> instance (for use in chained
154         * initialization).
155         */
156        public Builder in(InputStream in) {
157            this.in = in;
158            return this;
159        }
160
161        /**
162         * Output for the running evaluation (it's <code>System.out</code>).
163         * The controlling process and
164         * the remote process can share <code>System.out</code>.
165         * <p>
166         * The default, if this is not set, is <code>System.out</code>.
167         *
168         * @param out the <code>PrintStream</code> to be channelled to
169         * <code>System.out</code> in the remote execution process.
170         * @return the <code>Builder</code> instance (for use in chained
171         * initialization).
172         */
173        public Builder out(PrintStream out) {
174            this.out = out;
175            return this;
176        }
177
178        /**
179         * Error output for the running evaluation (it's
180         * <code>System.err</code>). The controlling process and the remote
181         * process can share <code>System.err</code>.
182         * <p>
183         * The default, if this is not set, is <code>System.err</code>.
184         *
185         * @param err the <code>PrintStream</code> to be channelled to
186         * <code>System.err</code> in the remote execution process.
187         * @return the <code>Builder</code> instance (for use in chained
188         * initialization).
189         */
190        public Builder err(PrintStream err) {
191            this.err = err;
192            return this;
193        }
194
195        /**
196         * Set a generator of temp variable names for
197         * {@link jdk.jshell.VarSnippet} of
198         * {@link jdk.jshell.Snippet.SubKind#TEMP_VAR_EXPRESSION_SUBKIND}.
199         * <p>
200         * Do not use this method unless you have explicit need for it.
201         * <p>
202         * The generator will be used for newly created VarSnippet
203         * instances. The name of a variable is queried with
204         * {@link jdk.jshell.VarSnippet#name()}.
205         * <p>
206         * The callback is sent during the processing of the snippet, the
207         * JShell state is not stable. No calls whatsoever on the
208         * <code>JShell</code> instance may be made from the callback.
209         * <p>
210         * The generated name must be unique within active snippets.
211         * <p>
212         * The default behavior (if this is not set or <code>generator</code>
213         * is null) is to generate the name as a sequential number with a
214         * prefixing dollar sign ("$").
215         *
216         * @param generator the <code>Supplier</code> to generate the temporary
217         * variable name string or <code>null</code>.
218         * @return the <code>Builder</code> instance (for use in chained
219         * initialization).
220         */
221        public Builder tempVariableNameGenerator(Supplier<String> generator) {
222            this.tempVariableNameGenerator = generator;
223            return this;
224        }
225
226        /**
227         * Set the generator of identifying names for Snippets.
228         * <p>
229         * Do not use this method unless you have explicit need for it.
230         * <p>
231         * The generator will be used for newly created Snippet instances. The
232         * identifying name (id) is accessed with
233         * {@link jdk.jshell.Snippet#id()} and can be seen in the
234         * <code>StackTraceElement.getFileName()</code> for a
235         * {@link jdk.jshell.EvalException} and
236         * {@link jdk.jshell.UnresolvedReferenceException}.
237         * <p>
238         * The inputs to the generator are the {@link jdk.jshell.Snippet} and an
239         * integer. The integer will be the same for two Snippets which would
240         * overwrite one-another, but otherwise is unique.
241         * <p>
242         * The callback is sent during the processing of the snippet and the
243         * Snippet and the state as a whole are not stable. No calls to change
244         * system state (including Snippet state) should be made. Queries of
245         * Snippet may be made except to {@link jdk.jshell.Snippet#id()}. No
246         * calls on the <code>JShell</code> instance may be made from the
247         * callback, except to
248         * {@link #status(jdk.jshell.Snippet) status(Snippet)}.
249         * <p>
250         * The default behavior (if this is not set or <code>generator</code>
251         * is null) is to generate the id as the integer converted to a string.
252         *
253         * @param generator the <code>BiFunction</code> to generate the id
254         * string or <code>null</code>.
255         * @return the <code>Builder</code> instance (for use in chained
256         * initialization).
257         */
258        public Builder idGenerator(BiFunction<Snippet, Integer, String> generator) {
259            this.idGenerator = generator;
260            return this;
261        }
262
263        /**
264         * Build a JShell state engine. This is the entry-point to all JShell
265         * functionality. This creates a remote process for execution. It is
266         * thus important to close the returned instance.
267         *
268         * @return the state engine.
269         */
270        public JShell build() {
271            return new JShell(this);
272        }
273    }
274
275    // --- public API ---
276
277    /**
278     * Create a new JShell state engine.
279     * That is, create an instance of <code>JShell</code>.
280     * <p>
281     * Equivalent to {@link JShell#builder() JShell.builder()}{@link JShell.Builder#build() .build()}.
282     * @return an instance of <code>JShell</code>.
283     */
284    public static JShell create() {
285        return builder().build();
286    }
287
288    /**
289     * Factory method for <code>JShell.Builder</code> which, in-turn, is used
290     * for creating instances of <code>JShell</code>.
291     * Create a default instance of <code>JShell</code> with
292     * <code>JShell.builder().build()</code>. For more construction options
293     * see {@link jdk.jshell.JShell.Builder}.
294     * @return an instance of <code>Builder</code>.
295     * @see jdk.jshell.JShell.Builder
296     */
297    public static Builder builder() {
298        return new Builder();
299    }
300
301    /**
302     * Access to source code analysis functionality.
303     * An instance of <code>JShell</code> will always return the same
304     * <code>SourceCodeAnalysis</code> instance from
305     * <code>sourceCodeAnalysis()</code>.
306     * @return an instance of {@link SourceCodeAnalysis SourceCodeAnalysis}
307     * which can be used for source analysis such as completion detection and
308     * completion suggestions.
309     */
310    public SourceCodeAnalysis sourceCodeAnalysis() {
311        if (sourceCodeAnalysis == null) {
312            sourceCodeAnalysis = new SourceCodeAnalysisImpl(this);
313        }
314        return sourceCodeAnalysis;
315    }
316
317    /**
318     * Evaluate the input String, including definition and/or execution, if
319     * applicable. The input is checked for errors, unless the errors can be
320     * deferred (as is the case with some unresolvedDependencies references),
321     * errors will abort evaluation. The input should be
322     * exactly one complete snippet of source code, that is, one expression,
323     * statement, variable declaration, method declaration, class declaration,
324     * or import.
325     * To break arbitrary input into individual complete snippets, use
326     * {@link SourceCodeAnalysis#analyzeCompletion(String)}.
327     * <p>
328     * For imports, the import is added.  Classes, interfaces. methods,
329     * and variables are defined.  The initializer of variables, statements,
330     * and expressions are executed.
331     * The modifiers public, protected, private, static, and final are not
332     * allowed on op-level declarations and are ignored with a warning.
333     * Synchronized, native, abstract, and default top-level methods are not
334     * allowed and are errors.
335     * If a previous definition of a declaration is overwritten then there will
336     * be an event showing its status changed to OVERWRITTEN, this will not
337     * occur for dropped, rejected, or already overwritten declarations.
338     * <p>
339     * The execution environment is out of process.  If the evaluated code
340     * causes the execution environment to terminate, this <code>JShell</code>
341     * instance will be closed but the calling process and VM remain valid.
342     * @param input The input String to evaluate
343     * @return the list of events directly or indirectly caused by this evaluation.
344     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
345     * @see SourceCodeAnalysis#analyzeCompletion(String)
346     * @see JShell#onShutdown(java.util.function.Consumer)
347     */
348    public List<SnippetEvent> eval(String input) throws IllegalStateException {
349        checkIfAlive();
350        List<SnippetEvent> events = eval.eval(input);
351        events.forEach(this::notifyKeyStatusEvent);
352        return Collections.unmodifiableList(events);
353    }
354
355    /**
356     * Remove a declaration from the state.
357     * @param snippet The snippet to remove
358     * @return The list of events from updating declarations dependent on the
359     * dropped snippet.
360     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
361     * @throws IllegalArgumentException if the snippet is not associated with
362     * this <code>JShell</code> instance.
363     */
364    public List<SnippetEvent> drop(PersistentSnippet snippet) throws IllegalStateException {
365        checkIfAlive();
366        checkValidSnippet(snippet);
367        List<SnippetEvent> events = eval.drop(snippet);
368        events.forEach(this::notifyKeyStatusEvent);
369        return Collections.unmodifiableList(events);
370    }
371
372    /**
373     * The specified path is added to the end of the classpath used in eval().
374     * Note that the unnamed package is not accessible from the package in which
375     * {@link JShell#eval()} code is placed.
376     * @param path the path to add to the classpath.
377     */
378    public void addToClasspath(String path) {
379        taskFactory.addToClasspath(path);  // Compiler
380        executionControl().commandAddToClasspath(path);       // Runtime
381    }
382
383    /**
384     * Attempt to stop currently running evaluation. When called while
385     * the {@link #eval(java.lang.String) } method is running and the
386     * user's code being executed, an attempt will be made to stop user's code.
387     * Note that typically this method needs to be called from a different thread
388     * than the one running the {@code eval} method.
389     * <p>
390     * If the {@link #eval(java.lang.String) } method is not running, does nothing.
391     * <p>
392     * The attempt to stop the user's code may fail in some case, which may include
393     * when the execution is blocked on an I/O operation, or when the user's code is
394     * catching the {@link ThreadDeath} exception.
395     */
396    public void stop() {
397        if (executionControl != null)
398            executionControl.commandStop();
399    }
400
401    /**
402     * Close this state engine. Frees resources. Should be called when this
403     * state engine is no longer needed.
404     */
405    @Override
406    public void close() {
407        if (!closed) {
408            closeDown();
409            executionControl().commandExit();
410        }
411    }
412
413    /**
414     * Return all snippets.
415     * @return the snippets for all current snippets in id order.
416     * @throws IllegalStateException if this JShell instance is closed.
417     */
418    public List<Snippet> snippets() throws IllegalStateException {
419        checkIfAlive();
420        return Collections.unmodifiableList(maps.snippetList());
421    }
422
423    /**
424     * Returns the active variable snippets.
425     * This convenience method is equivalent to <code>snippets()</code> filtered for
426     * {@link jdk.jshell.Snippet.Status#isActive status(snippet).isActive}
427     * <code>&amp;&amp; snippet.kind() == Kind.VARIABLE</code>
428     * and cast to <code>VarSnippet</code>.
429     * @return the active declared variables.
430     * @throws IllegalStateException if this JShell instance is closed.
431     */
432    public List<VarSnippet> variables() throws IllegalStateException {
433        return snippets().stream()
434                     .filter(sn -> status(sn).isActive && sn.kind() == Snippet.Kind.VAR)
435                     .map(sn -> (VarSnippet) sn)
436                     .collect(collectingAndThen(toList(), Collections::unmodifiableList));
437    }
438
439    /**
440     * Returns the active method snippets.
441     * This convenience method is equivalent to <code>snippets()</code> filtered for
442     * {@link jdk.jshell.Snippet.Status#isActive status(snippet).isActive}
443     * <code>&amp;&amp; snippet.kind() == Kind.METHOD</code>
444     * and cast to MethodSnippet.
445     * @return the active declared methods.
446     * @throws IllegalStateException if this JShell instance is closed.
447     */
448    public List<MethodSnippet> methods() throws IllegalStateException {
449        return snippets().stream()
450                     .filter(sn -> status(sn).isActive && sn.kind() == Snippet.Kind.METHOD)
451                     .map(sn -> (MethodSnippet)sn)
452                     .collect(collectingAndThen(toList(), Collections::unmodifiableList));
453    }
454
455    /**
456     * Returns the active type declaration (class, interface, annotation type, and enum) snippets.
457     * This convenience method is equivalent to <code>snippets()</code> filtered for
458     * {@link jdk.jshell.Snippet.Status#isActive status(snippet).isActive}
459     * <code>&amp;&amp; snippet.kind() == Kind.TYPE_DECL</code>
460     * and cast to TypeDeclSnippet.
461     * @return the active declared type declarations.
462     * @throws IllegalStateException if this JShell instance is closed.
463     */
464    public List<TypeDeclSnippet> types() throws IllegalStateException {
465        return snippets().stream()
466                .filter(sn -> status(sn).isActive && sn.kind() == Snippet.Kind.TYPE_DECL)
467                .map(sn -> (TypeDeclSnippet) sn)
468                .collect(collectingAndThen(toList(), Collections::unmodifiableList));
469    }
470
471    /**
472     * Returns the active import snippets.
473     * This convenience method is equivalent to <code>snippets()</code> filtered for
474     * {@link jdk.jshell.Snippet.Status#isActive status(snippet).isActive}
475     * <code>&amp;&amp; snippet.kind() == Kind.IMPORT</code>
476     * and cast to ImportSnippet.
477     * @return the active declared import declarations.
478     * @throws IllegalStateException if this JShell instance is closed.
479     */
480    public List<ImportSnippet> imports() throws IllegalStateException {
481        return snippets().stream()
482                .filter(sn -> status(sn).isActive && sn.kind() == Snippet.Kind.IMPORT)
483                .map(sn -> (ImportSnippet) sn)
484                .collect(collectingAndThen(toList(), Collections::unmodifiableList));
485    }
486
487    /**
488     * Return the status of the snippet.
489     * This is updated either because of an explicit <code>eval()</code> call or
490     * an automatic update triggered by a dependency.
491     * @param snippet the <code>Snippet</code> to look up
492     * @return the status corresponding to this snippet
493     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
494     * @throws IllegalArgumentException if the snippet is not associated with
495     * this <code>JShell</code> instance.
496     */
497    public Status status(Snippet snippet) {
498        return checkValidSnippet(snippet).status();
499    }
500
501    /**
502     * Return the diagnostics of the most recent evaluation of the snippet.
503     * The evaluation can either because of an explicit <code>eval()</code> call or
504     * an automatic update triggered by a dependency.
505     * @param snippet the <code>Snippet</code> to look up
506     * @return the diagnostics corresponding to this snippet.  This does not
507     * include unresolvedDependencies references reported in <code>unresolvedDependencies()</code>.
508     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
509     * @throws IllegalArgumentException if the snippet is not associated with
510     * this <code>JShell</code> instance.
511     */
512    public List<Diag> diagnostics(Snippet snippet) {
513        return Collections.unmodifiableList(checkValidSnippet(snippet).diagnostics());
514    }
515
516    /**
517     * For {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED} or
518     * {@link jdk.jshell.Snippet.Status#RECOVERABLE_NOT_DEFINED RECOVERABLE_NOT_DEFINED}
519     * declarations, the names of current unresolved dependencies for
520     * the snippet.
521     * The returned value of this method, for a given method may change when an
522     * <code>eval()</code> or <code>drop()</code> of another snippet causes
523     * an update of a dependency.
524     * @param snippet the declaration <code>Snippet</code> to look up
525     * @return the list of symbol names that are currently unresolvedDependencies.
526     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
527     * @throws IllegalArgumentException if the snippet is not associated with
528     * this <code>JShell</code> instance.
529     */
530    public List<String> unresolvedDependencies(DeclarationSnippet snippet) {
531        return Collections.unmodifiableList(checkValidSnippet(snippet).unresolved());
532    }
533
534    /**
535     * Get the current value of a variable.
536     * @param snippet the variable Snippet whose value is queried.
537     * @return the current value of the variable referenced by snippet.
538     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
539     * @throws IllegalArgumentException if the snippet is not associated with
540     * this <code>JShell</code> instance.
541     * @throws IllegalArgumentException if the variable's status is anything but
542     * {@link jdk.jshell.Snippet.Status#VALID}.
543     */
544    public String varValue(VarSnippet snippet) throws IllegalStateException {
545        checkIfAlive();
546        checkValidSnippet(snippet);
547        if (snippet.status() != Status.VALID) {
548            throw new IllegalArgumentException("Snippet parameter of varValue() '" +
549                    snippet + "' must be VALID, it is: " + snippet.status());
550        }
551        String value = executionControl().commandVarValue(maps.classFullName(snippet), snippet.name());
552        return expunge(value);
553    }
554
555    /**
556     * Register a callback to be called when the Status of a snippet changes.
557     * Each call adds a new subscription.
558     * @param listener Action to perform when the Status changes.
559     * @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription.
560     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
561     */
562    public Subscription onSnippetEvent(Consumer<SnippetEvent> listener)
563            throws IllegalStateException {
564        return onX(keyStatusListeners, listener);
565    }
566
567    /**
568     * Register a callback to be called when this JShell instance terminates.
569     * This occurs either because the client process has ended (e.g. called System.exit(0))
570     * or the connection has been shutdown, as by close().
571     * Each call adds a new subscription.
572     * @param listener Action to perform when the state terminates.
573     * @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription.
574     * @throws IllegalStateException if this JShell instance is closed
575     */
576    public Subscription onShutdown(Consumer<JShell> listener)
577            throws IllegalStateException {
578        return onX(shutdownListeners, listener);
579    }
580
581    /**
582     * Cancel a callback subscription.
583     * @param token The token corresponding to the subscription to be unsubscribed.
584     */
585    public void unsubscribe(Subscription token) {
586        synchronized (this) {
587            token.remover.accept(token);
588        }
589    }
590
591    /**
592     * Subscription is a token for referring to subscriptions so they can
593     * be {@linkplain JShell#unsubscribe unsubscribed}.
594     */
595    public class Subscription {
596
597        Consumer<Subscription> remover;
598
599        Subscription(Consumer<Subscription> remover) {
600            this.remover = remover;
601        }
602    }
603
604    // --- private / package-private implementation support ---
605
606    ExecutionControl executionControl() {
607        if (executionControl == null) {
608            this.executionControl = new ExecutionControl(new JDIEnv(this), maps, this);
609            try {
610                executionControl.launch();
611            } catch (IOException ex) {
612                throw new InternalError("Launching JDI execution engine threw: " + ex.getMessage(), ex);
613            }
614        }
615        return executionControl;
616    }
617
618    void debug(int flags, String format, Object... args) {
619        if (InternalDebugControl.debugEnabled(this, flags)) {
620            err.printf(format, args);
621        }
622    }
623
624    void debug(Exception ex, String where) {
625        if (InternalDebugControl.debugEnabled(this, 0xFFFFFFFF)) {
626            err.printf("Fatal error: %s: %s\n", where, ex.getMessage());
627            ex.printStackTrace(err);
628        }
629    }
630
631    /**
632     * Generate the next key index, indicating a unique snippet signature.
633     * @return the next key index
634     */
635    int nextKeyIndex() {
636        return nextKeyIndex++;
637    }
638
639    private synchronized <T> Subscription onX(Map<Subscription, Consumer<T>> map, Consumer<T> listener)
640            throws IllegalStateException {
641        Objects.requireNonNull(listener);
642        checkIfAlive();
643        Subscription token = new Subscription(map::remove);
644        map.put(token, listener);
645        return token;
646    }
647
648    private synchronized void notifyKeyStatusEvent(SnippetEvent event) {
649        keyStatusListeners.values().forEach(l -> l.accept(event));
650    }
651
652    private synchronized void notifyShutdownEvent(JShell state) {
653        shutdownListeners.values().forEach(l -> l.accept(state));
654    }
655
656    void closeDown() {
657        if (!closed) {
658            // Send only once
659            closed = true;
660            notifyShutdownEvent(this);
661        }
662    }
663
664    /**
665     * Check if this JShell has been closed
666     * @throws IllegalStateException if it is closed
667     */
668    private void checkIfAlive()  throws IllegalStateException {
669        if (closed) {
670            throw new IllegalStateException("JShell (" + this + ") has been closed.");
671        }
672    }
673
674    /**
675     * Check a Snippet parameter coming from the API user
676     * @param sn the Snippet to check
677     * @throws NullPointerException if Snippet parameter is null
678     * @throws IllegalArgumentException if Snippet is not from this JShell
679     * @return the input Snippet (for chained calls)
680     */
681    private Snippet checkValidSnippet(Snippet sn) {
682        if (sn == null) {
683            throw new NullPointerException("Snippet must not be null");
684        } else {
685            if (sn.key().state() != this) {
686                throw new IllegalArgumentException("Snippet not from this JShell");
687            }
688            return sn;
689        }
690    }
691
692}
693