JShell.java revision 3062:15bdc18525ff
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
26package jdk.jshell;
27
28import java.io.ByteArrayInputStream;
29import java.io.IOException;
30import java.io.InputStream;
31import java.io.PrintStream;
32import java.util.ArrayList;
33import java.util.Collections;
34import java.util.HashMap;
35import java.util.List;
36import java.util.Map;
37import java.util.Objects;
38import java.util.function.BiFunction;
39import java.util.function.Consumer;
40
41import java.util.function.Supplier;
42import jdk.internal.jshell.debug.InternalDebugControl;
43import static java.util.stream.Collectors.collectingAndThen;
44import static java.util.stream.Collectors.toList;
45import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
46import static jdk.jshell.Util.expunge;
47import jdk.jshell.Snippet.Status;
48
49/**
50 * The JShell evaluation state engine.  This is the central class in the JShell
51 * API.  A <code>JShell</code> instance holds the evolving compilation and
52 * execution state.  The state is changed with the instance methods
53 * {@link jdk.jshell.JShell#eval(java.lang.String) eval(String)},
54 * {@link jdk.jshell.JShell#drop(jdk.jshell.PersistentSnippet) drop(PersistentSnippet)} and
55 * {@link jdk.jshell.JShell#addToClasspath(java.lang.String) addToClasspath(String)}.
56 * The majority of methods query the state.
57 * A <code>JShell</code> instance also allows registering for events with
58 * {@link jdk.jshell.JShell#onSnippetEvent(java.util.function.Consumer) onSnippetEvent(Consumer)}
59 * and {@link jdk.jshell.JShell#onShutdown(java.util.function.Consumer) onShutdown(Consumer)}, which
60 * are unregistered with
61 * {@link jdk.jshell.JShell#unsubscribe(jdk.jshell.JShell.Subscription) unsubscribe(Subscription)}.
62 * Access to the source analysis utilities is via
63 * {@link jdk.jshell.JShell#sourceCodeAnalysis()}.
64 * When complete the instance should be closed to free resources --
65 * {@link jdk.jshell.JShell#close()}.
66 * <p>
67 * An instance of <code>JShell</code> is created with
68 * <code>JShell.create()</code>.
69 * <p>
70 * This class is not thread safe, except as noted, all access should be through
71 * a single thread.
72 * @see jdk.jshell
73 * @author Robert Field
74 */
75public class JShell implements AutoCloseable {
76
77    final SnippetMaps maps;
78    final KeyMap keyMap;
79    final TaskFactory taskFactory;
80    final InputStream in;
81    final PrintStream out;
82    final PrintStream err;
83    final Supplier<String> tempVariableNameGenerator;
84    final BiFunction<Snippet, Integer, String> idGenerator;
85
86    private int nextKeyIndex = 1;
87
88    final Eval eval;
89    final ClassTracker classTracker;
90    private final Map<Subscription, Consumer<JShell>> shutdownListeners = new HashMap<>();
91    private final Map<Subscription, Consumer<SnippetEvent>> keyStatusListeners = new HashMap<>();
92    private boolean closed = false;
93
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     * Return the status of the snippet.
473     * This is updated either because of an explicit <code>eval()</code> call or
474     * an automatic update triggered by a dependency.
475     * @param snippet the <code>Snippet</code> to look up
476     * @return the status corresponding to this snippet
477     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
478     * @throws IllegalArgumentException if the snippet is not associated with
479     * this <code>JShell</code> instance.
480     */
481    public Status status(Snippet snippet) {
482        return checkValidSnippet(snippet).status();
483    }
484
485    /**
486     * Return the diagnostics of the most recent evaluation of the snippet.
487     * The evaluation can either because of an explicit <code>eval()</code> call or
488     * an automatic update triggered by a dependency.
489     * @param snippet the <code>Snippet</code> to look up
490     * @return the diagnostics corresponding to this snippet.  This does not
491     * include unresolvedDependencies references reported in <code>unresolvedDependencies()</code>.
492     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
493     * @throws IllegalArgumentException if the snippet is not associated with
494     * this <code>JShell</code> instance.
495     */
496    public List<Diag> diagnostics(Snippet snippet) {
497        return Collections.unmodifiableList(checkValidSnippet(snippet).diagnostics());
498    }
499
500    /**
501     * For {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED} or
502     * {@link jdk.jshell.Snippet.Status#RECOVERABLE_NOT_DEFINED RECOVERABLE_NOT_DEFINED}
503     * declarations, the names of current unresolved dependencies for
504     * the snippet.
505     * The returned value of this method, for a given method may change when an
506     * <code>eval()</code> or <code>drop()</code> of another snippet causes
507     * an update of a dependency.
508     * @param snippet the declaration <code>Snippet</code> to look up
509     * @return the list of symbol names that are currently unresolvedDependencies.
510     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
511     * @throws IllegalArgumentException if the snippet is not associated with
512     * this <code>JShell</code> instance.
513     */
514    public List<String> unresolvedDependencies(DeclarationSnippet snippet) {
515        return Collections.unmodifiableList(checkValidSnippet(snippet).unresolved());
516    }
517
518    /**
519     * Get the current value of a variable.
520     * @param snippet the variable Snippet whose value is queried.
521     * @return the current value of the variable referenced by snippet.
522     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
523     * @throws IllegalArgumentException if the snippet is not associated with
524     * this <code>JShell</code> instance.
525     * @throws IllegalArgumentException if the variable's status is anything but
526     * {@link jdk.jshell.Snippet.Status#VALID}.
527     */
528    public String varValue(VarSnippet snippet) throws IllegalStateException {
529        checkIfAlive();
530        checkValidSnippet(snippet);
531        if (snippet.status() != Status.VALID) {
532            throw new IllegalArgumentException("Snippet parameter of varValue() '" +
533                    snippet + "' must be VALID, it is: " + snippet.status());
534        }
535        String value = executionControl().commandVarValue(maps.classFullName(snippet), snippet.name());
536        return expunge(value);
537    }
538
539    /**
540     * Register a callback to be called when the Status of a snippet changes.
541     * Each call adds a new subscription.
542     * @param listener Action to perform when the Status changes.
543     * @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription.
544     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
545     */
546    public Subscription onSnippetEvent(Consumer<SnippetEvent> listener)
547            throws IllegalStateException {
548        return onX(keyStatusListeners, listener);
549    }
550
551    /**
552     * Register a callback to be called when this JShell instance terminates.
553     * This occurs either because the client process has ended (e.g. called System.exit(0))
554     * or the connection has been shutdown, as by close().
555     * Each call adds a new subscription.
556     * @param listener Action to perform when the state terminates.
557     * @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription.
558     * @throws IllegalStateException if this JShell instance is closed
559     */
560    public Subscription onShutdown(Consumer<JShell> listener)
561            throws IllegalStateException {
562        return onX(shutdownListeners, listener);
563    }
564
565    /**
566     * Cancel a callback subscription.
567     * @param token The token corresponding to the subscription to be unsubscribed.
568     */
569    public void unsubscribe(Subscription token) {
570        synchronized (this) {
571            token.remover.accept(token);
572        }
573    }
574
575    /**
576     * Subscription is a token for referring to subscriptions so they can
577     * be {@linkplain JShell#unsubscribe unsubscribed}.
578     */
579    public class Subscription {
580
581        Consumer<Subscription> remover;
582
583        Subscription(Consumer<Subscription> remover) {
584            this.remover = remover;
585        }
586    }
587
588    // --- private / package-private implementation support ---
589
590    ExecutionControl executionControl() {
591        if (executionControl == null) {
592            this.executionControl = new ExecutionControl(new JDIEnv(this), maps, this);
593            try {
594                executionControl.launch();
595            } catch (IOException ex) {
596                throw new InternalError("Launching JDI execution engine threw: " + ex.getMessage(), ex);
597            }
598        }
599        return executionControl;
600    }
601
602    void debug(int flags, String format, Object... args) {
603        if (InternalDebugControl.debugEnabled(this, flags)) {
604            err.printf(format, args);
605        }
606    }
607
608    void debug(Exception ex, String where) {
609        if (InternalDebugControl.debugEnabled(this, 0xFFFFFFFF)) {
610            err.printf("Fatal error: %s: %s\n", where, ex.getMessage());
611            ex.printStackTrace(err);
612        }
613    }
614
615    /**
616     * Generate the next key index, indicating a unique snippet signature.
617     * @return the next key index
618     */
619    int nextKeyIndex() {
620        return nextKeyIndex++;
621    }
622
623    private synchronized <T> Subscription onX(Map<Subscription, Consumer<T>> map, Consumer<T> listener)
624            throws IllegalStateException {
625        Objects.requireNonNull(listener);
626        checkIfAlive();
627        Subscription token = new Subscription(map::remove);
628        map.put(token, listener);
629        return token;
630    }
631
632    private synchronized void notifyKeyStatusEvent(SnippetEvent event) {
633        keyStatusListeners.values().forEach(l -> l.accept(event));
634    }
635
636    private synchronized void notifyShutdownEvent(JShell state) {
637        shutdownListeners.values().forEach(l -> l.accept(state));
638    }
639
640    void closeDown() {
641        if (!closed) {
642            // Send only once
643            closed = true;
644            notifyShutdownEvent(this);
645        }
646    }
647
648    /**
649     * Check if this JShell has been closed
650     * @throws IllegalStateException if it is closed
651     */
652    private void checkIfAlive()  throws IllegalStateException {
653        if (closed) {
654            throw new IllegalStateException("JShell (" + this + ") has been closed.");
655        }
656    }
657
658    /**
659     * Check a Snippet parameter coming from the API user
660     * @param sn the Snippet to check
661     * @throws NullPointerException if Snippet parameter is null
662     * @throws IllegalArgumentException if Snippet is not from this JShell
663     * @return the input Snippet (for chained calls)
664     */
665    private Snippet checkValidSnippet(Snippet sn) {
666        if (sn == null) {
667            throw new NullPointerException("Snippet must not be null");
668        } else {
669            if (sn.key().state() != this) {
670                throw new IllegalArgumentException("Snippet not from this JShell");
671            }
672            return sn;
673        }
674    }
675
676}
677